一、背景描述
对于数组,大家肯定不陌生,众所周知c++中数组变量其实就是个指针,但笔者今天在阅读一个开源项目时发现一个奇怪的现象见下图,下面我们一起来探索下这里头到底有啥玄机呢?代码如下:
//对应的函数接受参数都是指针类型,但上图中一个传的参数是数组变量取地址,一个传的是数组变量
void bzero(void *, size_t) __POSIX_C_DEPRECATED(200112L);
ssize_t write(int __fd, const void * __buf, size_t __nbyte) __DARWIN_ALIAS_C(write);
二、数组与指针
因为上文中用到了数组和指针,所以我们简单通过一段代码来复习下
- 在很多用到数组名字的地方,编译器会自动将其替换为一个指向首元素的指针,如下图。
- 指向数组元素的指针可以执行所有迭代器元素,比如说递增,比较,两个指针相减等等。
- 和迭代器一样,两个指针相减的结果就是它们之间的距离,所以我们可以用来计算长度。
- cout对字符数组重载了
<<符,导致输出的整个数组的内容,如果想对打印字符串元素的地址得通过printf可以输出。
三、问题分析
那我们再回到我们问题上来看,细心的读者看到printf可以打印地址,那我们要不试打印下 &chs 和chs两者的地址呢?走说干就干
- 很惊呀!两者的地址居然一样。那按照这个结果看起来开源代码是没啥问题。
- 但是讲道理字符数组变量编译器不是替换为指向首元素的指针了么?再取一遍地址不应该是获取指向指针的地址么?为啥两者居然还一致呢? 带着疑惑笔者尝试进行了一遍汇编,看看这里头到底发生了啥。
具体产生汇编码的方法可以参考该文
.LC0:
.string "%p \n"
.text
.globl _Z10test_arrayv
.type _Z10test_arrayv, @function
main:
.LFB1197:
.cfi_startproc // 用在每个函数的开始,用于初始化一些内部数据结构
pushq %rbp // 保存旧的帧指针,相当于创建新的栈帧
.cfi_def_cfa_offset 16 // 修改计算 CFA 的规则。 寄存器保持不变,但偏移量是新的。 请注意,它是将添加到定义的寄存器以计算 CFA 地址的绝对偏移量。
.cfi_offset 6, -16 // 寄存器的先前值保存在 CFA 的偏移量处。
movq %rsp, %rbp //
.cfi_def_cfa_register 6
subq $16, %rsp
movb $97, -16(%rbp) // 将字符a放入栈地址-16上
movb $98, -15(%rbp)
movb $99, -14(%rbp)
movb $100, -13(%rbp)
movb $101, -12(%rbp)
movb $102, -11(%rbp)
leaq -16(%rbp), %rax // 将栈地址-16 传递给返回值
movq %rax, %rsi // 将返回值作为第二个参数传递
movl $.LC0, %edi // 将上面的$.LC0(见上面) 传递给通用寄存器
movl $0, %eax
call printf // 调用printf函数
leaq -16(%rbp), %rax // 将栈地址-16 传递给返回值
movq %rax, %rsi // 同上
movl $.LC0, %edi
movl $0, %eax
call printf
movl $0, %eax
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
从汇编码上看,在当做函数参数时,无论是对数组变量取地址,还是直接传递的是数组变量,其实本质上编译器都会替换为数组首元素的地址-编码中两次call printf时都有这么一样的指令 leaq -16(%rbp), %rax