首先当你遇到任何觉得是编译器有问题的情况时一定坚定一个信念:“编译器不会有问题”
这回我遇到的问题看上去就很像编译器有问题,但排查下来最终发现还是自己的问题,我将这个问题简化后是这样的:取出一个结构体中的成员的值,在有些文件中取出来是正确的,有些文件中取出来是不正确的。
复现方法如下,需要编写4个文件,这是在linux下测试的,但是在使用CCS编译DSP工程时发现的,知道原理了就会明白这个问题其实和平台无关。
root.h,只定义一个宏MACRO
1 #ifndef USER_ROOT_H_ 2 #define USER_ROOT_H_ 3 4 #define MACRO 5 6 #endif
header.h,包含条件编译代码,如果定义了MACRO,那么结构体成员中包含var2,否则不包含,注意这个文件中并没有包含root.h
1 #ifndef USER_HEADER_H_ 2 #define USER_HEADER_H_ 3 4 struct struct1 { 5 int var1; 6 #ifdef MACRO 7 int var2; 8 #endif 9 int var3; 10 }; 11 12 #endif
source.c 返回传入的结构体中var3成员的值
1 #include "header.h" 2 #include "root.h" 3 4 int function (struct struct1* p) 5 { 6 return p->var3; 7 }
main.c,定义一个结构体,分别给内部成员赋值为1,2,3,kk的值为结构体中成员var3的值,jj则调用function,但返回的也是结构体中var3的值,最后接下来打印两个数的值:
1 #include <stdio.h> 2 #include "root.h" 3 #include "header.h" 4 5 extern int function (struct struct1* p); 6 7 int main(void) 8 { 9 struct struct1 test; 10 11 test.var1 = 1; 12 test.var2 = 2; 13 test.var3 = 3; 14 15 int kk = test.var3; 16 int jj = function(&test); 17 18 printf("kk = %d\n", kk); 19 printf("jj = %d\n", jj); 20 21 return 0; 22 }
编译后运行结果如下图:
明显kk的值是正确的,而jj的值是2,这个值明明是var2成员的值啊,编译的时候既没有错误也没有警告,难道是编译器出问题了吗?
把header.h中的内容稍微修改一下:
1 #ifndef USER_HEADER_H_ 2 #define USER_HEADER_H_ 3 4 struct struct1 { 5 int var1; 6 #ifdef MACRO 7 int var2; 8 #warning "**********" 9 #endif 10 #warning "##########" 11 int var3; 12 }; 13 14 #endif
加入两条自己写的警告语句,然后再编译就会发现问题:
编译结果发现,编译source.c时只报了警告"##########",而编译main.c的时候报了"**********"和"##########"两种警告,这说明编译source.c的时候条件编译是不成立的,但是MACRO这个宏明明在root.h中定义了啊。这个时候就要注意头文件的包含问题了,在header.h中没有包含root.h,而在main.c中是先包含了root.h,再包含了header.h,但是在source.c中则是先包含header.h,再包含root.h,也就是说source.c中先将header.h中的内容展开,定义了结构体,然后再定义了MACRO宏,那么在source.c中的结构体编译时当然不满足条件编译的条件。
这里还有一个疑问,在source.c中明明写的是return p->var3;为什么实际返回值却是var2,即便确实存在头文件包含的问题,那编译器也不能自作主张将var3用var2代替呀。这里编译器其实并没有自作主张,因为头文件展开之后在main.c中和在source.c中定义的结构体是不同的,虽然这两个结构体的名字都是struct struct1,但是实际上这是两个不同的结构。function这个函数虽然传入的是main.c中的结构体,但是在source.c中却是以source.c中的结构体去解析的,因此source.c中返回的var3就是main.c中var2。
解决这个问题的方法就是要注意头文件包含的顺序。
最后总结一下,这是头文件包含的顺序所引发的问题,但是这里用结构体举例是因为它非常巧合,它编译时不会报任何错误和警告,但是运行时却发现有问题。而且问题很隐蔽,如果条件编译不是放在结构体中,那么调试运行时就很容易能发现这个问题,如果包含顺序不是和这个例子一样,那么要么是编译的时候直接会报错,要么是真的就不会出问题(但是程序员并没注意到包含顺序的问题)。
所以头文件包含要养成一个习惯,包含关系最好是树形的。