为什么在不同作用域中用相同名称声明的变量被分配了相同的内存地址?

Zac*_*ith 5 c

我知道char[]看过这篇文章:在C中重新声明变量是在while循环中声明变量的范围。

通过学习有关在中创建简单Web服务器的教程C,我发现我必须手动清除responseData在下面的示例中分配的内存,否则,的内容将index.html被连续地追加到响应中,并且响应中包含来自index.html以下内容的重复内容:

while (1)
{
  int clientSocket = accept(serverSocket, NULL, NULL);
  char httpResponse[8000] = "HTTP/1.1 200 OK\r\n\n";
  FILE *htmlData = fopen("index.html", "r");
  char line[100];
  char responseData[8000];
  while(fgets(line, 100, htmlData) != 0)
  {
      strcat(responseData, line);
  }
  strcat(httpResponse, responseData);
  send(clientSocket, httpResponse, sizeof(httpResponse), 0);
  close(clientSocket);
}
Run Code Online (Sandbox Code Playgroud)

更正依据:

while (1)
{
  ...
  char responseData[8000];
  memset(responseData, 0, strlen(responseData));
  ...
}
Run Code Online (Sandbox Code Playgroud)

来自JavaScript,这令人惊讶。为什么我要声明一个变量并可以访问在相同名称的不同范围中声明的变量的内存内容?为什么不C只是在后台重置内存?

另外...为什么在不同作用域中声明的同名变量被分配相同的内存地址?

根据这个问题:交替声明的变量具有与 ISN情况相同的内存地址模式。但是,我发现这种情况确实可靠。

Cia*_*Pan 8

不完全正确。您不需要清除整个responseData数组-清除其第一个字节就足够了:

 responseData[0] = 0;
Run Code Online (Sandbox Code Playgroud)

正如Gabriel Pellegrino在评论中指出的那样,一种更惯用的表达是

 responseData[0] = '\0';
Run Code Online (Sandbox Code Playgroud)

它通过零值代码点显式定义一个字符,而前者使用int常数零。在这两种情况下,右侧参数都具有类型int,该类型被隐式转换(截断)为char要分配的类型。(段落将px的注释固定为pmg的注释。)

您可以从strcat文档中知道:函数其第二个参数字符串附加到第一个参数字符串。如果您需要将第一个块存储到缓冲区中,则需要将其附加到空字符串中,因此需要确保缓冲区中的字符串为空。也就是说,它仅包含终止NUL字符。memset-整个阵列过大是徒劳的,因此浪费时间。

另外,strlen在阵列上使用a 会带来麻烦。您不知道为该数组分配的内存块的实际内容是什么。如果尚未使用或自上次使用与其他一些数据被覆盖,它可以包含任何 NULL字符。然后strlen将用尽数组,导致未定义行为。即使返回成功,它也会为您提供大于数组大小的字符串长度。结果memset将耗尽阵列,可能会覆盖一些重要数据!

每当使用数组时都使用sizeofmemset

memset(responseData, 0, sizeof(responseData));
Run Code Online (Sandbox Code Playgroud)

编辑

在上面,我试图解释如何用您的代码解决问题,但是我没有回答您的问题。他们来了:

  1. 为什么在不同作用域中的变量(...)被分配了相同的内存地址?

在执行方面,循环的每次迭代while(1) { ... }确实会创建一个新的作用域。但是,每个作用域在创建新作用域之前都会终止,因此编译器会在堆栈上保留适当的内存块,并且循环会在每次迭代中重新使用它。这也简化了已编译的代码:每次迭代都由完全相同的代码执行,该代码只是从末尾跳到开头。循环中所有访问局部变量的指令在每次迭代中都使用完全相同的寻址(相对于堆栈)。因此,下一个迭代中的每个变量在内存中的位置与所有先前迭代中的位置完全相同。

  1. 我发现我必须手动清除内存

是的,默认情况下,不在 C中初始化在堆栈上分配的自动变量。我们始终需要在使用初始值之前明确为其指定一个初始值–否则,该值是未定义的并且可能不正确(例如,浮点变量可能显示为非数字,字符数组可能显示为未终止,enum变量可能具有超出枚举定义的值,指针变量可能无法指向有效的可访问位置等)。

  1. 否则内容(...)会连续添加

上面已经回答了这个问题。

  1. 来自JavaScript,这令人惊讶

是的,JavaScript显然在新范围内创建了新变量,因此,每次您获得一个全新的数组时,它都是空的。在C语言中,您只需要为自动变量获取与先前分配的内存相同的区域,则您有责任对其进行初始化。

此外,请考虑两个连续的循环:

void test()
{
    int i;

    for (i=0; i<5; i++) {
        char buf1[10];
        sprintf(buf1, "%d", i);
    }

    for (i=0; i<1; i++) {
        char buf2[10];
        printf("%s\n", buf2);
    }
}
Run Code Online (Sandbox Code Playgroud)

第一个将五个数字的一​​位数字字符表示形式打印到字符数组中,每次都覆盖它-因此,buf1[](作为字符串)的最后一个值是"4"

您从第二个循环中期望什么输出?一般来说,我们不知道buf2[]将包含什么,并且printf-ing它会导致UB。但是,我们可以假设来自两个不相交作用域的相同变量集(即单个10个字符的字符数组)将在堆栈的同一部分中以相同的方式分配。如果是这种情况,我们将从4(正式未初始化的)数组中获得一个数字作为输出。

此结果取决于编译器的构造,应视为巧合。不要依赖它,因为这是UB!

  1. C为什么不只是在后台重置内存?

因为没有告诉。创建该语言是为了编译成有效,紧凑的代码。它在“幕后”中所做的尽可能少。除其他事情它并没有做的是不是,除非它告诉初始化自动变量。这意味着您需要在局部变量声明中添加一个显式的初始化程序,或者在首次使用之前添加一个初始化指令(例如,赋值)。(这不适用于模块范围的全局变量;默认情况下,这些变量被初始化为零。)

在高级语言中,某些或所有变量都是在创建时初始化的,但不是在C中初始化的。这是它的功能,我们必须忍受它-否则就不要使用这种语言。

  • 对于习惯用法,请更改为responseData [0] ='\ 0'; (2认同)

Gab*_*ino 6

用这行:

char responseData[8000];
Run Code Online (Sandbox Code Playgroud)

您在对编译器说:嗨,大C,给我一个8000字节的块并将其命名为responseData。

在运行时,如果您未指定,则不会有人清理或给您“全新”的内存。这意味着您在每次执行中获得的8000字节块可以容纳这8000字节中所有可能的位排列。可能发生的异常情况是,您在每次执行中都获得了相同的内存区域,因此,大C第一次给您的8000字节中的相同位。因此,如果您不清理,则会给人一种印象,即您使用的是同一变量,但事实并非如此!您只是在使用相同的(从未清理过的)内存区域。

我还要补充一点,如果有必要,以动态或静态方式清理分配的内存是程序员职责的一部分。