C语言传&str3给char* string[]形参为何能访问其他相邻字符串
C语言指针传参越界访问现象解析
示例代码与运行结果
测试代码如下:
#include <string.h> #include <stdio.h> #include <stdlib.h> void fn(char *string[]) { printf("string[0] - %p %s\n", string[0], string[0]); printf("string[1] - %p %s\n", string[1], string[1]); printf("string[2] - %p %s\n", string[2], string[2]); printf("string[3] - %p %s\n", string[3], string[3]); } int main(int argc, char *argv[]) { char *str1 = "AAAAA"; char *str2 = "XXXXX"; char *str3 = (char*)malloc(sizeof(char) * strlen(str1) + 1); strcpy(str3, "BBBBB"); char *str4 = "CCCCC"; fn(&str3); return 0; }
运行输出:
string[0] - 0x561c977682a0 BBBBB string[1] - 0x561c96884050 AAAAA string[2] - 0x561c96884056 XXXXX string[3] - 0x561c9688405c CCCCC
核心疑问
调用fn函数时仅传入了实参&str3,fn的形参定义为char *string[]。已知malloc分配的空间位于动态内存区(堆),str3指向该堆内存区域,而str1、str2、str4为指向字符串常量的指针,在内存中按地址递增顺序连续存放(str1<str2<str4)。为何仅传入str3的地址,fn函数却能依次访问到str1、str2、str4指向的字符串内容?&str3实参与char* string[]形参之间的内存关联逻辑是什么?
原理解释
- 函数形参的自动退化规则
C语言标准明确规定:当数组类型作为函数形参时,会被编译器自动调整为指向数组首元素类型的指针。因此形参写char *string[],和写char **string完全等价,fn函数本质只接收一个二级指针(指向char*类型变量的地址),并不会真的开辟一个指针数组的空间。 - 局部变量的栈存储布局
main函数中定义的str1、str2、str3、str4都是char*类型的局部变量,全部被分配在main函数的栈帧上。在当前的编译环境、编译选项下,这几个指针变量在栈上是连续存放的:str3变量的存储地址之后(向高地址方向),依次排列着str1、str2、str4三个指针变量的存储空间,每个空间都存着对应字符串常量/堆内存的首地址。 - 越界访问的实际逻辑
当传入&str3时,形参string的值就是栈上str3变量的地址:- 访问
string[0]等价于*(string + 0),即从str3的存储位置取出存的地址值,也就是堆上"BBBBB"的首地址,属于合法访问,结果符合预期。 - 访问
string[1]等价于*(string + 1),C语言指针运算会自动按指向类型的大小偏移:string是char**类型,偏移1单位的步长是一个char*的长度(64位系统下通常为8字节),刚好跳过str3的存储空间,读到栈上相邻位置存的str1的值,也就是"AAAAA"的首地址。 - 同理
string[2]、string[3]会继续向后偏移8字节,依次读到str2、str4存储的地址值,最终打印出对应的字符串。
- 访问
注意:这是典型的未定义行为,不属于C语言的标准特性。栈上局部变量的排布顺序受编译器版本、优化等级、栈保护机制、目标平台架构影响极大,调整任意编译参数、调换变量定义顺序甚至增加无关代码,都可能导致访问结果错乱,甚至触发段错误,生产环境绝对不能编写这类越界访问内存的代码。
内容的提问来源于stack exchange,提问作者Whistle1088
相关产品推荐
相关产品推荐

