在公司的代码中看到这样一段:
头文件中:
#ifndef cpu_info_h__
#define cpu_info_h__
//...
typedef struct sys_cpu_usage_* sys_cpu_usage_handle;
sys_cpu_usage_handle create_cpu_usage_calculator();
// ...
#endif // cpu_info_h__
结构体 sys_cpu_usage_ 在对应的源文件中才进行了定义,而对应的源文件会编译成共享库的形式。于是好奇当我自己编程时,包含了此头文件的源码为何在编译时不被编译器报错,因为编译器不知道 sys_cpu_usage_ 是个什么东西。
实验1:
#include <stdio.h>
typedef struct Test
{
int a;
int b;
}Test_t;
int main()
{
struct mystruct * hdl = calloc(1,8);
Test_t test = {1,2};
hdl = &test;
printf("hdl->a = %d\nhdl->b = %d\n", ((Test_t*)hdl)->a, ((Test_t *)hdl)->b);
}
最后成功的输出了a、b的值。
说明在声明hdl这个指针时,编译器只需要知道它是个指针,而不需要知道它指向的是什么类型,比如声明一个(void *)也是同样的效果。
只有当需要取结构体中的值时,编译器才会去寻找该结构体的定义,以此来对该结构进行“使用”。
如果在未定义结构体的情况下对结构体内数据进行访问,会报以下错误:
error: dereferencing pointer to incomplete type
说明该结构体类型未定义,但尝试去访问结构体内部的事物。编译器会报错。
实验2:
#include <stdio.h>
typedef struct Test
{
int a;
int b;
}Test_t;
int main()
{
struct mystruct * hdl = calloc(1,2);
Test_t test = {1,2};
memcpy(hdl,&test,8);
printf("sizeof(Test_t) = %d\nhdl->a = %d\nhdl->b = %d\n", sizeof(Test_t), ((Test_t*)hdl)->a, ((Test_t *)hdl)->b);
}
而以上的代码会生成如下的结果。
[root@masternode sysinfo]# ./test1
sizeof(Test_t) = 8
hdl->a = 1
hdl->b = 2
只为指针hdl申请了2字节的内存,但是把大小为8字节的变量test拷贝过去后,结构体仍能访问到全部的数值,此时发生了内存越界。
实验3:
#include <stdio.h>
typedef struct Test
{
int a1;
int a2;
int a3;
int a4;
int a5;
int a6;
int a7;
int a8;
int a9;
}Test_t;
int main()
{
struct mystruct * hdl = calloc(1,2);
Test_t test = {1,2,3,4,5,6,7,8,9};
memcpy(hdl,&test,36);
printf("hdl = %d\nhdl->a1 = %d\nhdl->a9 = %d\n", hdl, ((Test_t*)hdl)->a1, ((Test_t *)hdl)->a9);
Test_t * hdl_next = calloc(1,36);
memcpy(hdl_next,&test,36);
printf("hdl_next = %d\nhdl->a1 = %d\nhdl->a9 = %d\n", hdl_next, ((Test_t *)hdl)->a1, ((Test_t *)hdl)->a9);
}
输出如下:
[root@masternode sysinfo]# ./test1
hdl = 30826512
hdl->a1 = 1
hdl->a9 = 9
hdl_next = 30826544
hdl->a1 = 1
hdl->a9 = 1
可以看到结构体Test_t有9个int类型内容,也就是36字节的大小。可以认为calloc分配堆空间时,最小分配单位为32字节,也就是hdl_next比hdl多32。
虽然只为hdl申请了2字节的堆内存,但是实际上给了32字节,那么当memcpy时,32字节范围内的内容都不会被hdl_next污染。也就是说虽然hdl发生了越界(2字节存36字节),但前面的32字节都不会被hdl_next覆盖。
从32字节开始,hdl_next也存了自己的数据,hdl的a9就被hdl_next的a1覆盖掉了。
所以可以看到,在没有hdl_next时,hdl即使越界了,也没有被污染,它可以访问到正确的值。但是为hdl_next分配堆时,因为hdl的calloc只要了2字节,编译器认为32字节后的空间可以拿来分配(并不管上面是不是有数值),于是分配给了hdl_next,hdl_next赋值之后,就会把hdl后4个字节覆盖掉。