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

一、前言

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

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

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

下面一一道来。

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

二、问题描述

1. 正常的代码

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

typedef struct _Data1_
{int a;
}Data1;typedef struct _Data2_
{int b;struct _Data1_ *next;
}Data2;int main()
{Data1 d1 = {1};Data2 d2 = {2, &d1};printf("d1 = %p \n", &d1);printf("d2 = %p \n", &d2);}

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

$ gcc main.c -m32  -o main 
$ ./main 
d1 = 0xffdc72f0 
d2 = 0xffdc72f4

2. 错误的代码

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

typedef struct _Data2_
{int b;struct _Data3_ *next;
}Data2;

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

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

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

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

实际情况是:

$ gcc main.c -m32  -o main -I./ 
main.c: In function ‘main’:
main.c:18:20: warning: initialization from incompatible pointer type [-Wincompatible-pointer-types]Data2 d2 = {2, &d1};^
main.c:18:20: note: (near initialization for ‘d2.next’)
$ ./main 
d1 = 0xffd8ee70 
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

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

(2) g

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

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

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

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

typedef struct _Data1_
{int a;
}Data1;typedef struct _Data2_
{int b;void *next;
}Data2;int main()
{Data1 d1 = {1};    Data2 d2 = {2, &d1};Data1 *dn = d2.next;printf("dn->a = %d \n", dn->a);
}

编译、执行:

$ gcc main.c -m32  -o main -I./ 
$ ./main 
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 编译,继续报错:

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

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

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

四、总结

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

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

---------- End ----------

声明:

本文于网络整理,版权归原作者所有,如来源信息有误或侵犯权益,请联系我们删除或授权事宜。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/338723.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

oracle 11g b表空间什么情况下自动增加,Oracle 11g表空间——创建和扩展(永久)表空间...

Oracle 11g表空间——创建和扩展(永久)表空间本文内容创建(永久)表空间查看表空间扩展表空间创建(永久)表空间Oracle 按照区和段空间进行管理表空间。区管理方式 - 针对区的分配方式的不同,有两种方式:字典管理方式(dictionary-managed tablespace&#…

python rsa 公钥解密_python利用rsa库做公钥解密的方法教程

前言对于RSA的解密,即密文的数字的 D 次方求mod N 即可,即密文和自己做 D 次乘法,再对结果除以 N 求余数即可得到明文。D 和 N 的组合就是私钥(private key)。算法的加密和解密还是很简单的,可是公钥和私钥的生成算法却不是随意的…

oracle 11g release2版本jdbc,Oracle发布JDeveloper11gRelease2更新版

Oracle在周二的时候发布了JDeveloper IDE升级版JDeveloper 11g Release 2,该版本支持JSF 2.0技术和模块化的服务端Web UI开发;另外该版本在启动时间上和运行性能上也有很大改善,因为整个IDE重新架构了,基于OSGi后端结构&#xff0…

C语言:--位域和内存对齐

位域位域是指信息在保存时,并不需要占用一个完整的字节,而只需要占几个或一个二进制位。为了节省空间,C语言提供了一种数据结构,叫“位域”或“位段”。“位域“是把一个字节中的二进位划分为几个不同的区域,并说明每个…

python页面驱动mxd_如何利用python 批量导出mxd至jpg

展开全部你好,arcpy.mapping提供了如下的函32313133353236313431303231363533e78988e69d8331333335313835数:arcpy.mapping 函数AddLayer(data_frame, add_layer, {add_position})AddLayerToGroup(data_frame, target_group_layer, add_layer, {add_posi…

junit rule_使用@Rule在JUnit中测试文件和目录

junit rule多亏了TemporaryFolder Rule在JUnit中使用文件和目录进行测试很容易。 在JUnit中,规则( Rule )可以用作夹具设置和清除方法( org.junit.Before , org.junit.After , org.junit.BeforeClass和org…

oracle 递归计算,SQL(Oracle)中的递归计算

好吧,我想我已经有了解决方案.这些数字与你的数字有点不同,但我相当确定我的正在做你想要的.我们可以在第1步和第1步中完成所有工作. 2使用单个查询(main_sql).必须使用递归语句(recur_sql)完成3和4.with main_sql as (select a.*,b.*,sum(a_amt) over (partition by b_id) as …

C语言实现数据字节序交换的四种方式

1关于数据字节序的说明1)关于字节序的说明字节序有两种大端和小端。大端:数据高位存放在低地址,地位放在高地址。如0x12345678在内存中存放为地址从左到右为低到高12345678。 小端:数据地位存放在低地址,高位存放在高地…

python 抓取微博评论破亿_一篇文章教会你使用Python定时抓取微博评论

【Part1——理论篇】试想一个问题,如果我们要抓取某个微博大V微博的评论数据,应该怎么实现呢?最简单的做法就是找到微博评论数据接口,然后通过改变参数来获取最新数据并保存。首先从微博api寻找 抓取评论的接口,如下图…

cassandra 入门_Apache Cassandra和Java入门(第一部分)

cassandra 入门在此页面上,您将学到足够的知识以开始使用NoSQL Apache Cassandra和Java,包括如何安装,尝试一些基本命令以及下一步要做什么。 要求 要遵循本教程,您应该已经有一个正在运行的Cassandra实例,并且已经在…

C语言如何实现动态数组?

提出问题请问在c语言里如何实现动态大小的数组啊,比如说int a[N];,这里N的值可以在程序中定,或者有什么方法可以实现类似的功能?总之只要在编译时不用制定数组大小就行。分析问题嵌入式系统的内存是宝贵的,内存是否高效…

Oracle为什么装在XP系统,重装xp系统后oracle恢复方法

重装系统后oracle如何恢复呢?下面就给大家介绍一下重装系统后oracle的恢复方法1、我们安装数据库软件只需安装同版本的数据库软件,不需要创建数据库。最好安装在和原来数据库同样的%ORACLE_HOME%下,省得还要修改参数文件路径等。(直接覆盖原来的oracle即…

vba 当前文件名_VBA代码解决方案第77讲内容:如何导出文件

大家好,我们今日继续讲解VBA代码解决方案的第77讲内容:如何导出文件,形成一个文本文件,如果需要将工作表中的数据保存为文本文件,可以创建一个文本文件用于保存数据。应用于FileSystemObject对象的CreateTextFile方法创…

jboss调jvm参数_在同一台机器上启动多个JBoss A-MQ JVM

jboss调jvm参数因此,我最近没有写过很多博客-只是很忙。 但是,我新年的决心是写一些博客,分享一些我经常遇到的事情,即使这些简单的事情可能会帮助一些人。 因此,对于本条目,我将展示一种在单个计算机上启…

startup oracle 01012,ORA-01012:not logged on的解决办法

conn / as sysdba 报错ORA-01012: not logged on发生缘故原由:关闭数据库是shutdown 后面没有接关闭参数中的任何一个。nomal --->所有毗邻都断开时才气关闭;transactional --->守候事务竣事后,自动断开毗邻;immediate ---&…

常见的C语言字符串操作

#字符串倒序输出实现逻辑,通过strlen获取字符串长度,然后通过 len/2 进行交叉赋值,这里需要注意,不需要考虑len是奇数还是偶数的问题。如果len是奇数,最后一个字符就不需要倒序,如果是偶数,最后…

mui 时间样式错乱_微信公众号素材样式中心在哪?公众号动态分割线怎么添加?...

相比于静态分割线,动态分割线更有特色,能给文章增加趣味性。今天壹姐就来给公众号运营的小伙伴们介绍,怎么添加动态的分割线样式到文章里吧~1怎么使用公众号样式中心公众号后台的编辑功能比较基础,想要使用好看的公众号素材&#…

apache mesos_Apache Mesos:编写自己的分布式框架

apache mesos在上一篇文章中 ,我们了解了mesos是什么,它如何有用,并开始使用它。 在本文中,我们将看到如何在mesos上编写自己的框架。 (在mesos中,框架是在其上运行的任何应用程序。)本文介绍了…

php获取访问量文本形式,php利用用文本统计访问量的方法图文详解

这篇文章主要介绍了php使用文本统计访问量的方法,涉及php文本文件读写与数值运算的相关技巧,需要的朋友可以参考下方法1:$fp fopen("counter.txt", "r");while(!flock($fp, LOCK_EX)) { // acquire an exclusive lock// waiting to lock the f…

yolov4论文_Alexey 大神接棒,YOLOv4 重磅来袭!快来一睹论文真容吧!| 原力计划...

作者 | Mr.Jk.Zhang责编 | 夕颜出品 | CSDN(ID:CSDNnews)前言千呼万唤始出来系列,继YOLOv3两年后,YOLOv4终于在上周出来了,让我们来一睹论文真容吧!由于YOLO之父Jeseph Redmon在今年2月已宣布退出CV学术界,大家都以为Y…