【问题标题】:different outputs in c [closed]c中的不同输出[关闭]
【发布时间】:2018-05-29 20:51:51
【问题描述】:

这 4 个程序都创建了一个包含 10000 个字符元素的数组,并且都有不同的输出。谁能解释一下为什么?

数字 1:

int main()
{
char buffer[10000];
printf("address: %p\n", buffer);
return 0;
}

2号:

char buffer[10000];

int main()
{
    printf("address: %p\n", buffer);
    return 0;
}

3 号:

char buffer[10000] = { 'a', 'b', 'c', '\0' };
int main()
{
    printf("address: %p\n", buffer);
    return 0;
}

4 号:

int main()
{
    char* buffer = (char*)malloc(10000);
    printf("address: %p\n", buffer);
    free(buffer);
    return 0;
}

【问题讨论】:

  • %p 打印出buffer 在内存中的地址。为什么每次运行程序时都希望它是一样的?
  • 您实际上试图询问变量的不同链接方案,不是吗?
  • 你对变量的不同记忆了解多少?你知道“本地”、“全局”、“动态”、“bss”、“数据”、“显式初始化”的含义吗?你能用这些术语来描述你的程序之间的差异吗?您知道 C 标准中实际提到了多少这些内容吗?

标签: c output


【解决方案1】:

正如其他人提到的那样,原因是缓冲区的地址是 打印出来的,并不总是一样的。但问题是,为什么?

要回答这个问题,让我们猜测一下编译器会将缓冲区放在哪里 每个案例。

作为背景,程序集文件中有多个“部分”告诉 计算机生成的可执行文件中每个系列的 1 和 0 的含义。

其中一个部分是“数据”部分。这包含初始化的读/写 数据(在某些情况下它是只读的并且读/写有另一个名称)。

另一个部分是“文本”部分。这是只读的,包含 您正在运行的实际代码,转换为二进制。静态常量可以放 在这里,因为它可以在运行时访问。

第三部分是“bss”部分。这包含读/写数据,但 未初始化。当代码设置为运行时,计算机不必 不必担心将数据复制到此部分,只需确保它存在即可。

当代码执行时,这些部分中的每一个都将映射到内存中的某个地址 运行。这并没有描述您将看到的所有地址 然而,执行过程。还有两个!

“堆”是您的程序可以专门要求的内存的名称 在它开始运行之后。这很有用,因为您的程序可能不知道 在它开始之前它需要多少内存。

“堆栈”是程序启动时给内存的名称 从本质上节省临时工作。这是保存局部变量的地方。

因此,我们可以将正在运行的可执行文件的内存可视化为:

|_00_| Program Code
|_01_|
|_02_|
|_03_|
|....|
|_04_| Initialized Memory
|_05_|
|_06_|
|_07_|
|....|
|_08_| Uninitialized Memory
|_09_|
|_10_|
|_11_|
....
|_38_| Dynamic Memory (from malloc)
|_39_|
|_40_|
|_41_|
....
|_96_| Program Stack (local variables)
|_97_|
|_98_|
|_99_|

现在让我们看看每个例子。

1 号

缓冲区是在 main 的堆栈中声明的。因此缓冲区可能是 位于那里(尽管它足够大,一些编译器可能会移动它 别处)。堆栈通常位于可寻址末尾附近的内存中 向静态代码增长的内存。

2号

缓冲区是在全局范围内声明的。它是未初始化的,所以它可以进入 未初始化的数据部分。如果它被放在初始化数据部分 那么操作系统必须先初始化所有 10kb 的内存 启动代码,会浪费时间。

3 号

缓冲区已在全局范围内声明,但已初始化。它可以进入 初始化数据部分。操作系统将必须初始化所有 10kb 专用于“a”、“b”、“c”的缓冲区的内存,然后是一堆零。

4 号

缓冲区是通过请求更多内存来声明的,因此它将是一些块 操作系统允许它使用。

请注意,由于虚拟寻址,数字的结果可能 每次运行代码时,1-3 都是相同的,但几乎可以肯定的是 当操作系统找到不同的块时,在每次执行时进行更改 给你的记忆。

可视化所有这些,我们看到不同的地址:

|_00_| Program Code
|_01_|
|_02_|
|_03_|
|....|
|_04_| Initialized Memory
|_05_|
|_06_|
|_07_| <-- Number 3
|....|
|_08_| Uninitialized Memory
|_09_|
|_10_|
|_11_| <-- Number 2
....
|_38_| Dynamic Memory (from malloc)
|_39_|
|_40_|
|_41_| <-- Number 4
....
|_96_| Program Stack (local variables)
|_97_|
|_98_|
|_99_| <-- Number 1

【讨论】:

  • 每次运行程序时操作系统给出不同块的原因是ASLR
  • 是的,如果您获得不同的 PID,堆栈可能会有所不同。当 cmets 拥有“比必要的更深入”的东西时,我喜欢它。 :)
  • 为什么栈会依赖PID?在 ASLR 之前,每个进程都有相同的内存布局。
  • 在嵌入式系统中,每个 PID 通常都有自己的堆栈。 C 常用于嵌入式系统。
  • 啊,我通常假设我们谈论的是 Unix 或 Windows 等操作系统上的用户模式程序,其中进程在虚拟内存中运行,除非问题另有说明
【解决方案2】:

对于示例 2 和 3,数组是全局声明的,即在执行期间不在堆栈上。另外两个在程序运行时将其声明在堆栈上。这两种方案将产生截然不同的结果。每对结果之间的差异更加微妙,实际上取决于编译器。

【讨论】:

    【解决方案3】:

    分配的内存地址总是不同的,它取决于空闲内存和操作系统给你的程序的内存范围。

    编辑:还要考虑您在函数中分配的每个变量都分配在堆栈中,而其他变量则分配在堆中。

    【讨论】:

    • 这并没有解释四个不同程序之间的差异。我认为它因此有点忽略了问题的重点。
    【解决方案4】:

    printf("address: %p\n", buffer); 打印的文本对于所有程序可能都是相同的(尽管不太可能),并且正如 OP 所证明的那样,它们可能不同。

    许多编译器将数据排列在不同的组(例如段)中。这是编译器的属性,而不是 C。这些组通常基于变量的生命周期以及如何(如果)初始化。

    研究您的编译器以了解详细信息。

    char global_initialized_to_zero[10000];
    char global_initialized[10000] = { 'a', 'b', 'c', '\0' };
    
    int main() {
      char local_uninitialized[10000];
      char* allocated = malloc(10000);
      ...
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-02-25
      • 2011-12-10
      • 1970-01-01
      • 1970-01-01
      • 2021-04-13
      • 2021-04-06
      • 2018-11-13
      相关资源
      最近更新 更多