作为我们在编程语言学院培训的一部分,我们也学习了C.在测试期间,我们遇到了程序输出的问题:
#include <stdio.h>
#include <string.h>
int main(){
char str[] = "hmmmm..";
const char * const ptr1[] = {"to be","or not to be","that is the question"};
char *ptr2 = "that is the qusetion";
(&ptr2)[3] = str;
strcpy(str,"(Hamlet)");
for (int i = 0; i < sizeof(ptr1)/sizeof(*ptr1); ++i){
printf("%s ", ptr1[i]);
}
printf("\n");
return 0;
}
后来,在检查了答案之后,很明显细胞(&ptr2)[3]与&ptr1 [2]中的记忆细胞相同,所以程序的输出是:to be or not to be (Hamlet)
我的问题是,是否有可能只通过笔记本中的书面代码,而不检查任何编译器,知道某个指针(或一般所有变量)跟随或先于内存中的其他变量?
注意,我不是指数组变量,因此数组中的所有元素必须按顺序排列。
在这个声明中:
(&ptr2)[3] = str;
ptr2
被定义为char *ptr2
内部的main
。根据此定义,编译器负责为ptr2
提供存储。允许编译器使用它想要的任何存储 - 它可能在ptr1
之前,它可能在ptr1
之后,它可能是接近的,它可能很远。
然后&ptr2
取ptr2
的地址。这是允许的,但我们不知道该地址与ptr1
或其他任何地方的关系,因为允许编译器使用它想要的任何存储。
由于ptr2
是char *
,&ptr2
指向char *
,也被称为char **
。
然后(&ptr2)[3]
试图引用位于char *
的&ptr2
数组的元素3。但是在C的计算模型中没有数组。那里只有一个char *
。当没有数组的元素3时,当您尝试引用数组的3的元素时,行为不是由C标准定义的。
因此,这段代码就是一个糟糕的例子。看来测试作者误解了C,这段代码没有说明预期的内容。
char *ptr2 = some initializer;
(&ptr2)[3] = str;
当您评估&ptr2时,您将获得存储指向该初始化程序的指针的内存地址。
当您执行(&ptr2)[3]=something
时,您尝试在ptr2(字符串的地址)的位置进一步写入3*sizeof(void*)
位置。这是无效的,几乎可以肯定它完成了分段错误。
不,这是不可能的,也不能做出这样的假设。
通过在变量空间外写入,此代码调用未定义的行为,它基本上是“非法的”,并且当您运行它时会发生任何事情。 C语言规范没有说明可以利用某种特定顺序在堆栈上分配的变量,但它确实说访问随机存储器是未定义的行为。
基本上这段代码非常糟糕,不应该被使用,在教学环境中更是如此。这让我很难过,人们如何误解C并仍然教给别人。 :/
程序通常使用以下结构加载到内存中:堆栈,Mmap文件,堆,BSS(未初始化的静态变量),数据段(初始化静态变量)和文本(编译代码)
你可以在这里了解更多:https://manybutfinite.com/post/anatomy-of-a-program-in-memory/
根据您声明变量的方式,它将转到之前说过的地方之一。
编译器将根据编译时的意愿安排BSS和数据段变量,因此通常没有机会。堆旧变量(操作系统将获得更适合分配空间的内存块)
在堆栈(这是一个LIFO结构)中,变量被放在一起,所以如果你有:
int a = 5;
int b = 10;
你可以说a和b将一个接一个地放置。所以,在这种情况下,你可以告诉。
还有另一个例外,那就是如果变量是一个结构或一个数组,它们总是像我之前所说的那样放置,每个都跟在最后一个之后。
在你的代码中,ptr1是一个字符数组的数组,因此它将遵循我所说的异常。
实际上,请执行以下练习:
#include <stdio.h>
#include <string.h>
int main(){
const char * const ptr1[] = {"to be","or not to be","that is the question"};
for (int i = 0; i < 3; i++) {
for (int j = 0; j < strlen(ptr1[i]); j++)
printf("%p -> %c\n", &ptr1[i][j], ptr1[i][j]);
printf("\n");
}
}
你会看到内存地址及其内容!
祝你今天愉快。