C语言使用条件编译导致结构体赋值“不正确”的问题

发布时间 2023-12-21 00:50:55作者: Suzkfly

  首先当你遇到任何觉得是编译器有问题的情况时一定坚定一个信念:“编译器不会有问题”

  这回我遇到的问题看上去就很像编译器有问题,但排查下来最终发现还是自己的问题,我将这个问题简化后是这样的:取出一个结构体中的成员的值,在有些文件中取出来是正确的,有些文件中取出来是不正确的。

  复现方法如下,需要编写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。

  解决这个问题的方法就是要注意头文件包含的顺序。

  最后总结一下,这是头文件包含的顺序所引发的问题,但是这里用结构体举例是因为它非常巧合,它编译时不会报任何错误和警告,但是运行时却发现有问题。而且问题很隐蔽,如果条件编译不是放在结构体中,那么调试运行时就很容易能发现这个问题,如果包含顺序不是和这个例子一样,那么要么是编译的时候直接会报错,要么是真的就不会出问题(但是程序员并没注意到包含顺序的问题)。

  所以头文件包含要养成一个习惯,包含关系最好是树形的。