服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - C/C++ - C语言边角料:结构体中指针类型的成员变量,它的类型重要吗?

C语言边角料:结构体中指针类型的成员变量,它的类型重要吗?

2021-05-12 00:58IOT物联网小镇道哥 C/C++

昨天在编译代码的时候,之前一直OK的一个地方,却突然出现了好几个 Warning!本着强迫症要消灭一切警告的做法,最终定位到:是结构体内部, 指向结构体类型的指针成员变量导致的问题。

C语言边角料:结构体中指针类型的成员变量,它的类型重要吗?

  • 一、前言
  • 二、问题描述
  • 三、把类型改为 void 指针类型
  • 四、总结

一、前言

 

昨天在编译代码的时候,之前一直OK的一个地方,却突然出现了好几个 Warning!

本着强迫症要消灭一切警告的做法,最终定位到:是结构体内部, 指向结构体类型的指针成员变量导致的问题。

这个问题,也许永远不会碰到,之所以被我赶上了,应该是因为某个时候手贱, 误碰了键盘导致。

下面一一道来。

PS: 我的测试环境是 Ubuntu16.04-64,编译器使用系统自带的 gcc-5.4.0。

二、问题描述

 

1. 正常的代码

比较简单:结构体 struct _Data2_ 的第 2 个成员变量是一个指针,指向的数据类型是结构体 struct _Data1_。

  1. typedef struct _Data1_ 
  2.     int a; 
  3. }Data1; 
  4.  
  5. typedef struct _Data2_ 
  6.     int b; 
  7.     struct _Data1_ *next
  8. }Data2; 
  9.  
  10. int main() 
  11.     Data1 d1 = {1}; 
  12.     Data2 d2 = {2, &d1}; 
  13.  
  14.     printf("d1 = %p \n", &d1); 
  15.     printf("d2 = %p \n", &d2); 
  16.  

编译、执行,都没有问题:

  1. $ gcc main.c -m32  -o main  
  2. $ ./main  
  3. d1 = 0xffdc72f0  
  4. d2 = 0xffdc72f4 

2. 错误的代码

现在我们来模拟误碰键盘操作,把 struct _Data2_ 中 next 成员指向的数据类型,改为一个 不存在的结构体:

  1. typedef struct _Data2_ 
  2.     int b; 
  3.     struct _Data3_ *next
  4. }Data2; 

在测试代码中,struct _Data3_ 肯定是不存在的。

好了,现在执行编译指令 gcc main.c -m32 -o main,将会得到什么结果?

可以停下来稍微 思考一下。

我之前的预期是:gcc 会 报错,找不到 struct _Data3_ 这个类型。

实际情况是:

  1. $ gcc main.c -m32  -o main -I./  
  2. main.c: In function ‘main’: 
  3. main.c:18:20: warning: initialization from incompatible pointer type [-Wincompatible-pointer-types] 
  4.      Data2 d2 = {2, &d1}; 
  5.                     ^ 
  6. main.c:18:20: note: (near initialization for ‘d2.next’) 
  7. $ ./main  
  8. d1 = 0xffd8ee70  
  9. d2 = 0xffd8ee74 

好神奇吧, gcc 居然不报错!那么我们就按照 gcc 的方式来理解一下。

我们知道,编译器在遇到一个结构体类型的时候,最重要的就是需要知道结构体类型 所占据的内存空间的大小。

gcc 在遇到 struct _Data2_ 这个字符串时,判断出它是一个用户自定义的数据类型:结构体 _Data2。

gcc 继续读取结构体内部的每一个字符,在读取到 *next 时,知道它是一个 指针。

此时它并并没确认该指针所指向的数据类型是否存在,它只是为 next 保留了 4 个字节的内存空间(32位系统)。

然后 gcc 在解析 Data2 d2 = {2, &d1}; 这一行时,就发现 类型不匹配了:data2 的 next 需要的是 struct _Data3_ 类型的指针,但是赋值的 d1 是 struct _Data1_ 类型,于是给出警告信息。

我们用其他的编译器试一下:

(1) clang

  1. $ clang main.c -m32  -o main -I./  
  2. main.c:18:20: warning: incompatible pointer types initializing 'struct _Data3_ *' with an expression of type 'Data1 *' 
  3.       (aka 'struct _Data1_ *') [-Wincompatible-pointer-types] 
  4.     Data2 d2 = {2, &d1}; 
  5.                    ^~~ 
  6. 1 warning generated. 
  7. $ ./main  
  8. d1 = 0xffb1b3a0  
  9. d2 = 0xffb1b398 

(2) g++

  1. $ g++ main.c -m32  -o main -I./  
  2. main.c: In function ‘int main()’: 
  3. main.c:18:23: error: cannot convert ‘Data1* {aka _Data1_*}’ to ‘_Data3_*’ in initialization 
  4.      Data2 d2 = {2, &d1}; 

看起来,只有 g++ 进一步确认了 _Data3_ 这个结构体类型不存在!

三、把类型改为 void 指针类型

 

把 struct _Data2_ 中的 next 成员,改为 指向 void 型的指针,然后在 main 函数中操作它。

  1. typedef struct _Data1_ 
  2.     int a; 
  3. }Data1; 
  4.  
  5. typedef struct _Data2_ 
  6.     int b; 
  7.     void *next
  8. }Data2; 
  9.  
  10. int main() 
  11.     Data1 d1 = {1};     
  12.     Data2 d2 = {2, &d1}; 
  13.      
  14.     Data1 *dn = d2.next
  15.     printf("dn->a = %d \n", dn->a); 

编译、执行:

  1. $ gcc main.c -m32  -o main -I./  
  2. $ ./main  
  3. dn->a = 1 

可以看到:Data1 *dn = d2.next; 这一行把指向 void 型的 d2.next 赋值给指向Data1型的指针变量 dn,然后在 printf 语句中可以正确地打印出dn中的成员变量a。

这又回到了指针的本质: 指针就是一个地址,至于如何来解释这个地址中的内容,这是由定义这个指针时所指定的数据类型来决定的

结合代码来看:虽然d2.next是一个 void 型指针,但是它的确存储了一个 地址(变量 d1 的地址)。然后把这个地址赋值给dn 指针,那么通过dn指针来操作该地址内的成员时,就取决于在定义dn时所指定的数据类型(Data1),因此 dn->a 就可以正确的从这个地址中取出前 4 个字节,然后作为一个int型的数据打印出来。

以上代码,如果使用clang来编译,结果也是正确的。

用g++编译,继续报错:

  1. $ g++ main.c -m32  -o main -I./  
  2. main.c: In function ‘int main()’: 
  3. main.c:23:20: error: invalid conversion from ‘void*’ to ‘Data1* {aka _Data1_*}’ [-fpermissive] 
  4.      Data1 *dn = d2.next

如果想让这个错误消除掉,在指针赋值时, 强制转换一下即可(把void型指针强转成Data1型指针,然后再赋值):

  1. Data1 *dn = (Data1 *)d2.next

四、总结

 

这里描述的错误,几乎很少遇到,除非是像我一样误碰了键盘。

不过,从中我们也看到了一个现象:gcc编译器在面对结构体时,主要关心的是结构体在内存空间中所占用的空间大小,对其内部指向结构体类型的指针,并没有严格的检查是否存在,g++ 在这一点就做的严谨一些了。

原文链接:https://mp.weixin.qq.com/s/YdgZH6HG_rN14GZ2me6O7w

延伸 · 阅读

精彩推荐
  • C/C++关于C语言中E-R图的详解

    关于C语言中E-R图的详解

    今天小编就为大家分享一篇关于关于C语言中E-R图的详解,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看...

    Struggler095962021-07-12
  • C/C++OpenCV实现拼接图像的简单方法

    OpenCV实现拼接图像的简单方法

    这篇文章主要为大家详细介绍了OpenCV实现拼接图像的简单方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    iteye_183805102021-07-29
  • C/C++C语言实现双人五子棋游戏

    C语言实现双人五子棋游戏

    这篇文章主要为大家详细介绍了C语言实现双人五子棋游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    两片空白7312021-11-12
  • C/C++深入C++拷贝构造函数的总结详解

    深入C++拷贝构造函数的总结详解

    本篇文章是对C++中拷贝构造函数进行了总结与介绍。需要的朋友参考下...

    C++教程网5182020-11-30
  • C/C++c/c++内存分配大小实例讲解

    c/c++内存分配大小实例讲解

    在本篇文章里小编给大家整理了一篇关于c/c++内存分配大小实例讲解内容,有需要的朋友们可以跟着学习参考下。...

    jihite5172022-02-22
  • C/C++c/c++实现获取域名的IP地址

    c/c++实现获取域名的IP地址

    本文给大家汇总介绍了使用c/c++实现获取域名的IP地址的几种方法以及这些方法的核心函数gethostbyname的详细用法,非常的实用,有需要的小伙伴可以参考下...

    C++教程网10262021-03-16
  • C/C++C语言main函数的三种形式实例详解

    C语言main函数的三种形式实例详解

    这篇文章主要介绍了 C语言main函数的三种形式实例详解的相关资料,需要的朋友可以参考下...

    ieearth6912021-05-16
  • C/C++使用C++制作简单的web服务器(续)

    使用C++制作简单的web服务器(续)

    本文承接上文《使用C++制作简单的web服务器》,把web服务器做的功能稍微强大些,主要增加的功能是从文件中读取网页并返回给客户端,而不是把网页代码...

    C++教程网5492021-02-22