1.前言2.问题描述3.改类型为void指针类型4.总结1.前言昨天编译代码的时候,之前还可以的地方突然出现了几个Warning!排除所有warning的做法最后定位:问题出在结构体内部指向结构体类型的指针成员变量。这个问题可能永远不会遇到。之所以追上来,应该是因为某个时候误触了键盘。让我们一一道来。PS:我的测试环境是Ubuntu16.04-64,编译器使用系统自带的gcc-5.4.0。二、问题描述1、正常代码比较简单:结构体struct_Data2_的第二个成员变量是一个指针,指向的数据类型是结构体struct_Data1_。typedefstruct_Data1_{inta;}Data1;typedefstruct_Data2_{intb;struct_Data1_*next;}Data2;intmain(){Data1d1={1};Data2d2={2,&d1};printf("d1=%p\n",&d1);printf("d2=%p\n",&d2);}编译执行,没有问题:$gccmain.c-m32-omain$./maind1=0xffdc72f0d2=0xffdc72f42。错误代码下面我们来模拟误触键盘操作,将struct_Data2_中next成员指向的数据类型改为不存在的结构:typedefstruct_Data2_{intb;struct_Data3_*next;}Data2;测试代码中,struct_Data3_肯定不存在。好了,现在执行编译命令gccmain.c-m32-omain,结果会怎样呢?你可以停下来想一想。我之前的预期是:gcc会报错,找不到struct_Data3_的类型。实际情况是:$gccmain.c-m32-omain-I./main.c:Infunction'main':main.c:18:20:warning:initializationfromincompatiblepointertype[-Wincompatible-pointer-types]Data2d2={2,&d1};^main.c:18:20:note:(nearinitializationfor'd2.next')$./maind1=0xffd8ee70d2=0xffd8ee74太神奇了,gcc居然不报错!那我们就按照gc??c的方式来理解吧。我们知道,编译器在遇到结构类型时,最重要的是要知道该结构类型占用的内存空间大小。当gcc遇到字符串struct_Data2_时,判断为用户自定义数据类型:structure_Data2。gcc继续读取结构内部的每一个字符,当它读取到*next时,就知道它是一个指针。此时并不会确认指针指向的数据类型是否存在,只是为next(32位系统)预留4字节的内存空间。然后gcc在解析Data2d2={2,&d1};这一行时,发现类型不匹配:data2的next需要一个struct_Data3_类型的指针,但是赋值的d1是struct_Data1_类型的,所以warning消息给出。让我们用其他编译器试试:(1)clang$clangmain.c-m32-omain-I./main.c:18:20:warning:incompatiblepointertypesinitializing'struct_Data3_*'withanexpressionoftype'Data1*'(aka'struct_Data1_*')[-Wincompatible-pointer-types]Data2d2={2,&d1};^~~1warninggenerated.$./maind1=0xffb1b3a0d2=0xffb1b398(2)g++$g++main.c-m32-omain-I./main.c:Infunction'intmain()':main.c:18:23:error:cannotconvert'Data1*{aka_Data1_*}'to'_Data3_*'initializationData2d2={2,&d1};貌似,只有g++进一步确认结构类型_Data3_不存在!3、将类型改为void指针类型将struct_Data2_中的下一个成员改为void类型的指针,然后在main函数中对其进行操作。typedefstruct_Data1_{inta;}Data1;typedefstruct_Data2_{intb;void*next;}Data2;intmain(){Data1d1={1};Data2d2={2,&d1};Data1*dn=d2.next;printf("dn->a=%d\n",dn->a);}编译执行:$gccmain.c-m32-omain-I./$./maindn->a=1可以看到:Data1*dn=d2。下一个;这行将指向void类型的d2.next赋值给指向Data1类型的指针变量dn,然后在printf语句中就可以正确打印出dn中的成员变量a了。这就回到了指针的本质:指针就是地址。至于如何解释这个地址的内容,是由定义这个指针时指定的数据类型决定的。结合代码:d2.next虽然是一个void类型的指针,但是它确实存储了一个地址(变量d1的地址)。然后将这个地址赋给dn指针,那么通过dn指针操作地址中的成员时,取决于定义dn时指定的数据类型(Data1),所以dn->a可以正确的从这个地址开始取出4个字节,然后作为int类型的数据打印出来。上面的代码,如果用clang编译,结果也是正确的。用g++编译继续报错:$g++main.c-m32-omain-I./main.c:Infunction'intmain()':main.c:23:20:error:invalidconversionfrom'void*'到'Data1*{aka_Data1_*}'[-fpermissive]Data1*dn=d2.next;如果要消除这个错误,在给指针赋值的时候,强制转换(强制将void指针转换为Data1指针,然后再赋值)即可:Data1*dn=(Data1*)d2.next;4.总结一下这里描述的错误,很少遇到,除非你像我一样误触键盘。但是,我们也看到了一个现象:gcc编译器在面对结构体时,主要关心的是结构体在内存空间中所占空间的大小,对于结构体的内部指针类型并没有严格的限制。检查是否存在,g++在这方面比较严谨。本文转载自微信公众号“IOT物联网小镇”,可通过以下二维码关注。转载本文请联系物联小镇公众号。
