我正在运行以下代码并发现了一些我不明白的东西:
main(){
char *s1 = "hi";
char *s2 = "Bye";
char *s3 = "Out";
printf("s %p | p %p : s %p | p %p : s %p | p %p", s1, &s1, s2, &s2, s3, &s3);
}
输出结果为:
s 0000000000409020 | p 000000000064FE58 : s 0000000000409023 | p 000000000064FE50 : s 0000000000409027 | p 000000000064FE48
在这里,我们可以清楚地看到字符串与其起始地址一起按值的递增顺序存储,但指针变量的地址按值的递减顺序存储 - 在内存中。
我只是好奇为什么会这样? 指针变量的地址不应该也按递增顺序排列吗?
存储在s1
、s2
和s3
中的地址的数值(即初始化为"hi"
、"Bye"
和"Out"
的字符数组的地址(保证不相等,并且它们可能彼此接近,但不要求它们相邻或按任何特定顺序排列。 类似地,地址&s1
、&s2
和&s3
(即局部变量s1
、s2
和s3
的地址(的数值保证不相等,并且可能彼此接近,但不需要相邻或按任何特定顺序排列。
s1
、s2
和s3
保存字符串文字的地址。 字符串文字是具有静态存储持续时间的常量数组的语法糖,这意味着它们都是在程序开始运行之前分配和初始化的。 由于它们不是具有静态存储持续时间的同一阵列,1因此它们不能具有相同的地址,但除此之外,从技术上讲,它们可以位于 RAM 中的任何位置。 实际上,它们将被放入为只读数据预留的RAM区域中,这意味着它们将彼此靠近,但是编译器和链接器会为了方便它们而布置该区域,您不应该依赖它们是如何做到的。
&s1
、&s2
和&s3
是局部变量的地址。 同样,由于它们不都是同一个变量,因此它们不能具有相同的地址,并且再次,它们可能彼此接近,因为它们都属于同一个函数,但同样,它们在内存中的地址的确切关系取决于编译器,您不应该依赖它。 熟悉汇编语言的人通常认为局部变量会一次推送一个到硬件堆栈上,因此它们应该根据"堆栈增长的方向">2连续,但编译代码不会这样做。 编译器更方便的是,在进入每个函数时移动堆栈指针一次,然后将其保留在那里,从而创建称为"堆栈帧"的东西。 这意味着局部变量实际上同时出现,它们在堆栈帧中的地址可以是编译器最方便的任何地址。 (例如,它们可以按大小排序,以便最小的变量最接近堆栈指针,并且可以以较短的位移访问。
1C 标准中有一个特殊情况,它允许字符串被"重复数据删除",即如果你在一个函数的顶部写const char *a = "foo";
,而在另一个函数的顶部写const char *b = "foo";
,存储在a
和b
中的地址最终可能会相同。 但是您的字符串并不相同,因此不能以这种方式处理它们。
2有趣的事实:C 实现根本不需要硬件堆栈,更不用说向特定方向增长的堆栈了! 它需要支持递归函数调用,但它如何做到这一点是完全没有指定的。
字符串存储在程序的R/O - Data section
中。它们可以按编译器选择的任何顺序存储。 局部变量存储在堆栈上,虽然这些变量也可以按任何顺序存储,但堆栈在内存中增长,而堆在内存中增长。