首页 > 手机 > 配件 > Rust的胖指针到底胖在栈上还是堆上,objdump

Rust的胖指针到底胖在栈上还是堆上,objdump

来源:整理 时间:2022-04-02 16:11:42 编辑:华为40 手机版

C语言中复制一个结构体只能使用memcpy的方法吗?感觉有些麻烦,有别的方法吗?

C语言中复制一个结构体只能使用memcpy的方法吗感觉有些麻烦,有别的方法吗

谢邀。这个问题和我之前发的文章有些相似,上周我在我的C语言学习圈子里简要介绍了一个小窍门,粗略来说就是使用C语言结构体的赋值语法,代替memcpy()语句,以精简代码,大致如下图所示:有读者看到后,认为C语言结构体的赋值并不等价于 memcpy,也有朋友评论说 b=a 是“浅拷贝”,还有读者提到结构体赋值效率没有memcpy高,那么 b = a 语句被执行后,究竟发生了什么呢?编写测试C语言代码得到答案最简单直接的方法就是实验,因此这里给出一段较为完整的C语言代码,用于测试结构体的赋值语句,如下所示。

为了讨论主题,下面C语言代码比较精简:上面这段C语言代码很简单,main() 函数定义了 3 个结构体变量 a, b, c,其中 a 被初始化为 {3, 5},并通过赋值语句拷贝给 b,memcpy() 拷贝给 c。考察 a,b,c 占用的内存里的值,从最终“拷贝效果”上分析赋值语句和memcpy()的异同。

查看内存值查看上述C语言程序中的变量 a, b, c 的值方法很多,最直接的方法就是使用 printf() 函数逐字节打印,不过这样就略显繁琐了,使用 GDB 工具调试C语言程序更简单些。首先,输入 gcc t.c -g 编译上述C语言代码,得到可执行文件 a.out。接着,就可以使用 gdb 调试了:首先在 main() 函数处下断点,然后输入 run 命令让C语言程序运行起来:可以发现程序停在第 10 行了,此时变量 a,b, c 还没有被赋值或者 memcpy。

我们先看一下结构体 s 的 size,可以直接在 gdb 环境查看:发现 sizeof(struct s) 等于 16,这主要是因为C语言编译器为了提升效率,对结构体 s 的两个成员做了内存对齐处理。所以,虽然 char 型的 c 成员实际上只需 1 个字节内存空间,但是因为成员 l 占用 8 字节内存空间,所以编译器在 c 后面预留了 7 个字节。

读者 @Romi1984 认为 C语言结构体赋值拷贝和 memcpy 拷贝不等价,因为“赋值的话,对齐字节不会拷贝”。他的意思应该是 c 后面预留的 7 个字节不会被拷贝,那是不是如此呢?在执行 b =a; 语句之前,我们先来查看 a,b,c 在内存里的值:能够看出,此时变量 a,b,c 的内存值并不完全相同。

输入 next 命令,使C语言程序运行到第 16 行,也即 return 0; 语句处,此时赋值语句以及 memcpy 语句都被执行完毕,再查看 a,b,c 的内存值,得到如下输出:发现变量 a, b, c 的值完全相同,包括结构体 s 的 c 成员后内存对齐的 7 个字节,这说明读者 @Romi1984 说的“对齐字节不会被拷贝”是不准确的,至少就本例而言,C语言结构体 s 的赋值拷贝和 memcpy 拷贝效果上是等价的。

效率问题虽然通过 gdb 查看内存值,我们发现C语言结构体的赋值拷贝和 memcpy 拷贝效果是等价的,但是,读者 @quser225816904 认为,这两种方式的效率是不一样的。那究竟是否如此呢?得到答案最直接的办法就是衡量这两条语句的执行时间。不过由于这一“执行时间”很短,难以计量,我们采取其他方法:输入下面的命令,查看C语言程序的汇编代码。

# objdump -dS a.out 从C语言程序的汇编代码可以看出,b = a; 和 memcpy() 语句都是 4 条 mov 语句,这说明两种拷贝方式的效率相差无几,所以读者 @quser225816904 的说法也是不准确的。另外,从C语言程序的汇编代码也能更直观的看出 b = a; 和 memcpy() 是等价的。

读者也可以通过多次执行 b = a 和 memcpy 语句,对比两种拷贝方式的效率。“深拷贝”和“浅拷贝”前面两位读者分别从执行效果和执行效率两个角度质疑了C语言结构体赋值拷贝和memcpy拷贝的等价性,也有读者认为赋值拷贝只是“浅拷贝”,那么究竟是否如此呢?首先,先要明白“浅拷贝”和“深拷贝”概念,这两个概念 Java,C ,js 等编程语言程序员应该比较熟悉,在C语言中倒是不怎么常提。

细究这两个概念的区别并不是本文的重点,所以这里粗略的对“浅拷贝”和“深拷贝”做如下区分,对于把变量 a 拷贝给 b:如果拷贝后,b 的内容完全等于 a,并且两个变量在内存中是独立的,则称此次拷贝为“深拷贝”。如果靠背后,只是通过 b 能够访问 a 中的内容,a 的内容改变时,b 的“内容”也随之改变,则称此次拷贝为“浅拷贝”。

这样看来,就本例而言,b = a;显然是一次“深拷贝”,因为 a, b 在内存中彼此独立,并且拷贝后,b 的内容和 a 的内容完全相同。那C语言的结构体赋值拷贝一定是“深拷贝”吗?我们将结构体 s 新增一个指针成员 buf:对 a 的初始化也做相应修改,相关C语言代码如下,请看:为了讨论主题,上述C语言代码没有做错误处理。

现在 b = a; 还是“深拷贝”吗?读者如果做了实验,应该会发现,b 的 buf 成员本身在内存中的确独立于 a 的 buf 成员,但是它指向的内存却与 a 的 buf 成员指向的内存是同一块,所以这时 b = a; 不再是纯粹的“深拷贝”了。小结本节主要讨论了C语言结构体的赋值语法可以用于拷贝,并针对之前读者的几个典型问题做了较为详细的实例探讨。

不过,C语言是一门非常灵活的编程语言,可能同样的一条语句,在不同的环境下执行结果是不一样的,这一点本文最后的讨论就是一个实例。应该明白,本文举的例子仅是为了抛砖引玉,展示遇到问题该如何分析的方法,学习C语言,应该乐于做实验尝试才对。欢迎在评论区一起讨论,质疑。文章都是手打原创,每天最浅显的介绍C语言、linux等嵌入式开发,喜欢我的文章就关注一波吧,可以看到最新更新和之前的文章哦。

c语言程序经过编译后,每条指令都有一个内存地址,那两个程序如果有相同内存地址的指令怎么办?

c语言程序经过编译后,每条指令都有一个内存地址,那两个程序如果有相同内存地址的指令怎么办

把一个程序自己的代码、数据、堆栈看成一块完整的砖头。编译器就是造砖机,安排好这一块砖内部的结构。裸奔的系统就这一块砖,不会发生相互覆盖的矛盾。如果跑多任务,砖头就多了。但这时通常会引入操作系统来管理砖头。堆砌砖头时会给每块砖头不一样的起始坐标,确保不发生两块砖头占一个空间的情况。早期的dos操作系统规定每块砖头高度不超过1M。

编译器做出来的每块砖内部都是用偏移地址0000h—ffffh来区分单元,但是操作系统在往物理内存搬砖堆砌时,会给每块砖不同的段起始地址,和偏移地址加在一起才是物理地址。这样就不会发生重叠了。当然这里也有编译器的功劳。编译器除了造砖,还会生成一个规格标签,用于通知操作系统这砖有多大多厚,让操作系统能好好堆砖不浪费空间。

不同操作系统的这种规格标签格式不一样,因此windows程序是不能直接跑在linux下的。后来的处理器出现了mmu,内存管理单元,程序的地址空间和物理地址空间的换算就更复杂了。但是保证不同程序各自空间不发生覆盖还是基本要求。一个程序能覆盖别的程序空间,大概是病毒恶意代码才做的事情。cpu的安全特性越来越强,这种情况很少见了吧。

文章TAG:Rust胖在objdump指针

最近更新