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

时间:2019-05-08 20:07:50

标签: c

我知道在看过这篇文章Redeclaring variables in C之后,在while循环中声明char[]变量是有作用的。

在完成有关在C中创建简单Web服务器的教程之后,我发现在以下示例中我必须手动清除分配给responseData的内存,否则将清除{{1 }}只是连续地附加到响应中,并且响应中包含来自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);
}

来自JavaScript,这令人惊讶。为什么我要声明一个变量并可以访问在同一个名称的不同范围中声明的变量的内存内容?为什么while (1) { ... char responseData[8000]; memset(responseData, 0, strlen(responseData)); ... } 不会只在后台重置该内存?

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

根据以下问题:Variable declared interchangebly has the same pattern of memory address并非如此。但是,我发现这种情况确实可靠。

3 个答案:

答案 0 :(得分:6)

此行:

constructor(props) {
    super(props);
    this.authenticated = props.authenticated;
    //this.handleClick = this.handleClick.bind(this);
} 

state ={
    page : []
}

componentDidMount() {
    let config = null;
    //let token = null;
    app.auth().currentUser.getIdToken(true).then(function(idToken) {
        config = {
            headers: {'Authorization': idToken}
        };
        console.log(config);
        axios.get('http://localhost:4001/api/v1/page',config)
        .then(res => {
            console.log(res.data);
          const page = res.data;
          this.setState( page );
        }).catch((err)=>{
            console.log(err);
        });

     })

  }

render () {

    const authenticated = this.authenticated;
    console.log(authenticated);
    console.log(this.state);

    return (
        <h1> test 123</h1>
      );
}

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

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

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

答案 1 :(得分:6)

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

 responseData[0] = 0;

加布里埃尔·佩莱格里诺(Gabriel Pellegrino)在the comment中指出,更惯用的表达方式是

 responseData[0] = '\0';

它通过零值的代码点显式定义一个字符,而前者使用int常数零。在这两种情况下,右侧参数的类型均为int,该类型将隐式转换(截断)为char类型以进行赋值。 (段落将thx固定为pmg的comment。)

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

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

每当memset数组时都使用 sizeof

memset(responseData, 0, sizeof(responseData));

编辑

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

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

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

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

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

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

上面已经回答了这个。

  1. 来自JavaScript,这令人惊讶

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

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

enum

第一个将五个数字的一​​位数字字符表示打印到字符数组中,每次都覆盖它-因此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); } } (作为字符串)的最后一个值为buf1[]。 / p>

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

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

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

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

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

答案 2 :(得分:3)

  

为什么要声明一个变量并可以访问在同一个名称的不同作用域中声明的变量的内存内容?为什么C不会只是在后台重置内存?

存储持续时间为auto的对象(即块范围变量)不会自动初始化-它们的初始内容是不确定的。请记住,C是1970年代初期的产品,并且在运行时速度方面偏重于便利性。 C语言的哲学是程序员最擅长知道是否应将某事物初始化为一个已知值,并且足够聪明,可以根据需要自己进行操作。

虽然您在逻辑上 在每次循环迭代中创建和销毁responseData的新实例,但事实证明,每次重复使用相同的内存位置。我们喜欢认为在进入块时为每个块范围对象分配了空间,并在离开块时将其释放,但实际上(通常)并非如此- all 块范围的空间函数中的对象在函数入口分配,并在函数exit 1 释放。

不同作用域中的不同对象 可能映射到幕后的相同内存。考虑类似

void bletch( void )
{
  if ( some_condition )
  {
    int foo = some_function();
    printf( "%d\n", foo );
  } 
  else
  {
    int bar = some_other_function();
    printf( "%d\n", bar );
  }

foobar不可能同时存在,因此没有理由为两者分配单独的空间-编译器通常会为一个{{1}分配空间}对象进入函数入口,该空间将用于intfoo,具体取决于采用哪个分支。

因此,bar发生的事情是,在函数入口上分配了一个 8000个字符的数组的空间,并且循环的每次迭代都使用相同的空间。这就是为什么您需要在每次迭代中将其清除,无论是通过responseData调用还是使用初始化程序(如

memset


  1. 正如MM在注释中指出的那样,对于可变长度数组(以及可能的其他可变修改类型)而言,这不是正确的-那些 的空间已根据需要预留,尽管该空间在哪里不是从语言定义中指定的。但是,对于所有其他类型,常规惯例是在函数条目上分配所有必要的空间。