当前位置:网站首页>【精华文】C语言结构体特殊情况分析:结构体指针 / 基本数据类型指针,指向其他结构体
【精华文】C语言结构体特殊情况分析:结构体指针 / 基本数据类型指针,指向其他结构体
2022-08-09 11:03:00 【XV_】
参考链接:Structure pointer pointing to different structure instance
注:可以查看此篇的问题和唯一的回复,那是相对正确的,不要看comment,有很多错误。
我是拒绝分析这种问题的,因为似乎没有人会这么乱用,但是……在华保健老师的编译原理示例代码和Linux0.11内核中,就遇到了这么神奇的代码,那就不得不研究一下了!毕竟是大神写的代码,我不知道应该是我渣。
1 测试代码
#include <stdio.h>
#include <stdlib.h>
struct A {
char a;
int b;
};
struct B {
int c;
int d;
};
struct C {
int e;
char f;
};
int main() {
struct A a = {
'a', 100 };
struct B b = {
101, 300 };
struct C c = {
200,'c' };
// 根据字节对齐,都占据8字节
printf("A: size %d %c %d\n", sizeof(a), a.a, a.b);
printf("B: size %d %d %d\n", sizeof(b), b.c, b.d);
printf("C: size %d %d %c\n", sizeof(c), c.e, c.f);
struct A *ap = &b; // A结构体指针,指向结构体B
printf("%d %d\n",ap->a, ap->b);
printf("%c %d\n", ap->a, ap->b);
char *chp = &b;
chp[1] = 'b'; // 这块区域其实是字节对齐导致的空闲空间
printf("%d %d\n", ap->a, ap->b);
printf("%c %d\n", ap->a, ap->b);
/* 如何访问这块内存,取决于ap指针,能访问多大地方,取决于内存区域本身 */
ap->a = 'c'; // ap->a = 'c'就是相当于 char a = 'c';
ap->a = 1000; // ap->a = 1000 就是相当于 char a = 1000; 1000过大会被截断高位
ap->b = 3000; // ap->b <=> int b ...
struct C *cp = &b; // C结构体指针,指向结构体B
printf("%d %d\n", cp->e, cp->f);
printf("%d %c\n", cp->e, cp->f);
cp->e = 3000;
cp->f = 'e';
cp->f = 1000;
// 整形指针指向结构体A
int *bp = &a;
bp[0] = 1000;
bp[1] = 2000;
printf("A: %c %d\n", a.a, a.b);
printf("A: %d %d\n", a.a, a.b);
bp[2] = 2000; // 可以修改内存,但是堆栈溢出,
// 因为该空间没有被分配(局部变量是保存在堆栈中的)
return 0;
}
2 结构体占据空间问题 & 字节对齐
struct A {
char a;
int b;
};
struct B {
int c;
int d;
};
struct C {
int e;
char f;
};
...
struct A a = {
'a', 100 };
struct B b = {
101, 300 };
struct C c = {
200,'c' };
// 根据字节对齐,都占据8字节
printf("A: size %d %c %d\n", sizeof(a), a.a, a.b);
printf("B: size %d %d %d\n", sizeof(b), b.c, b.d);
printf("C: size %d %d %c\n", sizeof(c), c.e, c.f);
...
运行以上程序,我们可以直到,三个结构体分别创建了一个变量,并且每个结构体占据的空间大小都是8字节。
至于为什么都是8字节,这是内存对齐问题,不展开说明了,我们看看这几个结构体被分配的空间情况吧。
- 每个结构体都占8字节的内存空间
- 红色部分表示实际占用的空间
- 蓝色部分表示空闲空间
注意:这就意味着,凡是被分配的8字节空间,是可以任意访问的,而空间外面是不允许访问的。
让结构体A的指针ap,指向结构体B的变量b
现在我们建立一个结构体A的指针,让其指向b。
struct A *ap = &b; // A结构体指针,指向结构体B
printf("%d %d\n",ap->a, ap->b);
printf("%c %d\n", ap->a, ap->b);
我们看看内存的情况,再分析一下打印的结果。
上面是内存的分布情况,现在
- 访问
ap->a
打印出来的是:101
,e
- 访问
ap->b
打印出来的是300
所以ap指针实际访问的应该是下面重点标出的部分:
而这部分,是不是很熟悉?
所以,ap指针尽管指向了结构体B,但是实际还是按照结构体A的结构访问内存的。
2.1 使用char指针指向结构体B
刚才我们发现,使用结构体A的指针,可以直接访问结构体B,那么,如果是基本数据类型呢?我们试一下。
char *chp = &b;
chp[1] = 'b'; // 这块区域其实是字节对齐导致的空闲空间
printf("%d %d\n", ap->a, ap->b);
printf("%c %d\n", ap->a, ap->b);
我们看到内存分布如上图,现在执行chp[1] = 'b'
(b的ASCII码是62)
之后就变成了:
哦!这是令人惊讶的,char类型的指针指向了一块内存区域,然后使用下标修改了内存的值!
还记得动态数组申请吗?和内个是一样的原理!
int *a = (int *)malloc(sizeof(int) * 10);
a[0] = 1; // 使用下标访问
a[1] = 2;
...
free(a);
告诉我们两件事
- 指针默认指向最开始的元素,索引是0
- 使用下标索引可以依次访问后面的元素,每次向后移动的内存数,取决于指针的数据类型
所以上面的事情不难理解。
然后我们继续执行程序
printf("%d %d\n", ap->a, ap->b);
printf("%c %d\n", ap->a, ap->b);
尽管之前的空闲空间改变了,但是结果依然不变,也就是说我们之前的说法是正确的。
再进一步验证
/* 如何访问这块内存,取决于ap指针,能访问多大地方,取决于内存区域本身 */
ap->a = 'c'; // ap->a = 'c'就是相当于 char a = 'c';
ap->a = 1000; // ap->a = 1000 就是相当于 char a = 1000; 1000过大会被截断高位
ap->b = 3000; // ap->b <=> int b ...
结果显而易见,对于ap->a = 1000
,尽管1000已经超过了1字节大小,但是最终只修改了第一个字节,这就好比char a = 1000
一样,a = 0xe8
是的,1000 = 0x3e8,但是只有一个字节,所以最高位的3被舍弃了。
2.2 用结构体C指针cp指向结构体B
struct C *cp = &b; // C结构体指针,指向结构体B
printf("%d %d\n", cp->e, cp->f);
printf("%d %c\n", cp->e, cp->f);
cp->e = 3000;
cp->f = 'e';
cp->f = 1000;
我们再试一试!
最终结果显而易见。
2.3 用int指针指向结构体A
// 整形指针指向结构体A
int *bp = &a;
bp[0] = 1000;
bp[1] = 2000;
printf("A: %c %d\n", a.a, a.b);
printf("A: %d %d\n", a.a, a.b);
bp[2] = 2000; // 可以修改内存,但是堆栈溢出,
// 因为该空间没有被分配(局部变量是保存在堆栈中的)
其实这个事情我们之前干过了,之前用char,现在用int再干一下。
这个事情进一步说明了什么呢?
- a提供了有限的8字节内存空间
- bp指针能够修改哪里,取决于它指向的地址;一次修改多大空间,取决于它数据类型的大小
- 指针不能修改未被分配的空间,最后
bp[2]
访问了外界空间,因此产生了
因为局部变量都是被分配在栈中的,现在这个局部变量访问越界了,产生了错误,栈被破坏。
栈破坏这里情况非常复杂,先粗浅理解为,使用了未分配的空间导致了错误吧。
Linux0.11 内核中,使用上述方法,实现了GDT和IDT。
3 小结:精华在这里
分析了这么多,最终小结一下吧。
我们的眼中只有两件事
- 已分配的内存空间
- 某数据类型的指针
现在,我们就让指针指向内存空间的起始地址,然后就可以操作这个内存空间了。
再增加一些限制
- 内存空间就这么大,不能访问外面
- 指针每次访问的地址,是通过下标访问的,一次只能移动数据类型大小的整数倍
这个时候你眼中的C语言,分配一块内存,再创建一个指针,打遍天下无敌手!
当然了,除了特殊情况一般没人这么干,你会疯掉,看你代码的人也会疯掉!
4 补充:直接深入底层,看汇编代码
之前我们的分析是基于C语言层级的,比较抽象,实际上,编译完成之后的汇编语言,一看就明白了。
你可以看到ap->a
直接访问的是byte
,而ap->b
访问的是dword
,一个是字节,一个是双字,大小自然清晰。
这也是编译器的功能,把C语言提供的,方便人类使用的大量抽象,给翻译成方便机器使用的少量指令的复杂排列组合。
5 什么叫打遍天下无敌手呢?
其实就是瞎玩儿吧……但是的确可以这么干的!我们试一试。
int main() {
char aaa[4] = {
1,2,3,4 };
char aaa2[4] = {
1,2,3,4 };
int *bbb = &aaa;
printf("\n\n%x\n\n\n", bbb[0]);
return 0;
}
会打印什么呢?显而易见的!内存是01 02 03 04
,然后一个int *
指针访问了它,打印04030201
。
我们可以使用bbb[0]
或者*b
都行,因为b指向起始地址。
那,能不能通过bbb[1]
访问aaa2
的内存呢?
不行! 因为aaa1
和aaa2
是两个数组变量,他们在内存中的位置不是连续的,是随机的,如果你想达到内种效果,那就是前面提到的结构体了,把这两个放进一个结构体里面,就是连续分配内存了,就能使用bbb[1]
了。
最后,记住只有两件事
- 一块已分配的内存
- 一个指针
边栏推荐
- Numpy常用操作博客合集
- 解决1.tensorflow运行使用CPU不使用GPU 2.tensorflow环境下的GPU版本号 3.tensorflow和cuda以及cudnn版本对应问题 4.查看cuda和cudnn版本
- 【VIBE: Video Inference for Human Body Pose and Shape Estimation】论文阅读
- Quartz的理解
- PTA 计算天数
- Preparation for gold three silver four: how to successfully get an Ali offer (experience + interview questions + how to prepare)
- kubernetes中不可见的OOM
- cnn的输入输出
- b站up主:空狐公子 --矩阵求导(分母布局)课程笔记
- 商业技术解决方案与高阶技术专题 - 数据可视化专题
猜你喜欢
CentOS6.5 32bit安装Oracle-11gR2步骤说明
linux mysql操作的相关命令
性能测试(03)-JDBC Request
CentOS6.5 32bit安装Oracle、ArcSde、Apache等配置说明
CSDN的markdown编辑器语法完整大全
ThreadLocal及其内存泄露分析
支付宝小程序的接入
信息系统项目的十大管理
性能测试(01)-jmeter元件-线程组、调试取样器
Preparation for gold three silver four: how to successfully get an Ali offer (experience + interview questions + how to prepare)
随机推荐
PTA 找出不是两个数组共有的元素
Getting Started with MNIST Machine Learning
Create a table in a MySQL database through Doc
ThreadLocal及其内存泄露分析
threejs+shader 曲线点运动,飞线运动
Since I use the HiFlow scene connector, I don't have to worry about becoming a "dropper" anymore
Qt获取EXE可执行文件的上一级目录下的文件
备战金三银四:如何成功拿到阿里offer(经历+面试题+如何准备)
WebSocket
Error: Cannot find module ‘./application‘
PoseNet: A Convolutional Network for Real-Time 6-DOF Camera Relocalization Paper Reading
Qt读写.ini配置文件
activemq 消息持久化
matlab图像分割,从基因芯片荧光图像中提取阴性点(弱)和阳性点(强)
激光条纹中心提取——灰度重心法
中断系统结构及中断控制
pip common commands and changing source files
【Subpixel Dense Refinement Network for Skeletonization】CVPR2020论文解读
1006 Sign In and Sign Out (25分)
faster-rcnn learn