【问题标题】:memory allocation for string and array of char字符串和char数组的内存分配
【发布时间】:2011-06-25 21:37:08
【问题描述】:

我不明白下面的代码是如何分配内存的:

#include<stdio.h>
#include<string.h>

int main()
{
    char a[]={"text"};
    char b[]={'t','e','x','t'};
    printf(":%s: sizeof(a)=%d, strlen(a)=%d\n",a, sizeof(a), strlen(a));
    printf(":%s: sizeof(b)=%d, strlen(b)=%d\n",b, sizeof(b), strlen(b));
    return 0;
}

输出是

:text: sizeof(a)=5, strlen(a)=4
:texttext: sizeof(b)=4, strlen(b)=8

通过查看内存地址和输出代码,变量 b 似乎位于变量 a 之前,这就是 strlen(b) 通过查找 \0 返回 8 的原因。 为什么会这样?我希望首先声明变量 a。

【问题讨论】:

  • 您不能保证以任何特定顺序分配内存。 a 可能出现在 b 之前或之后,或者它们可能相距甚远。
  • 是的。这就是为什么它在堆栈中更高的原因。但你真的不应该依赖这样的期望。
  • 将 strlen() 与字符数组一起使用可能很危险。
  • @JustAnotherProgrammer:不会比在分配的内存上调用 strlen 更危险。

标签: c string memory-management


【解决方案1】:

语言不保证什么放在哪里。所以,你的实验意义不大。它可能有效,也可能无效。行为未定义。您的 b 不是字符串,将 strlen 与非字符串一起使用是 UB。

不过,从纯粹实用的角度来看,局部变量通常分配在堆栈上,并且可能现代平台(如 x86)上的堆栈向后增长,即从较高地址到较低地址。因此,如果您使用其中一个平台,您的编译器可能会决定按照变量声明的顺序分配变量(a 第一个和b 第二个),但是因为堆栈向后增长b 最终在内存中的地址低于a。 IE。 b 在内存中结束之前 a

人们可以注意到,典型的实现通常不会为局部变量一个接一个地分配堆栈空间。相反,所有局部变量(堆栈帧)的整个内存块是一次分配的,这意味着我上面描述的逻辑不一定适用。然而,编译器仍然有可能仍然遵循局部变量布局的“反向”方法,即前面声明的变量稍后放置在局部内存框架中,“好像”它们按顺序一个接一个地分配他们的声明。

【讨论】:

    【解决方案2】:

    您的“b”字符数组不是以空值结尾的。要理解,请考虑 char a[] 声明等效于:

    char a[] = { 't', 'e', 'x', 't', '\0' };
    

    换句话说,strlen(b) 是未定义的,它只是在随机内存中查找 NULL 字符(0 字节)。

    【讨论】:

    • 在我看来,这个问题清楚地表明 OP 知道这一点。问题实际上是“为什么 a 高于 b?”
    • @Pascal:是他的编译器还是别的什么,看我的回答。
    【解决方案3】:

    在我的 ideone sn-p 上看到的输出不同:http://ideone.com/zHhHc

    :text: sizeof(a)=5, strlen(a)=4
    :text
    

    当我使用键盘时,我看到的输出与您不同:http://codepad.org/MXJWY136

    :text: sizeof(a)=5, strlen(a)=4
    :text: sizeof(b)=4, strlen(b)=4
    

    另外,当我用 C++ 编译器编译它时,我得到相同的输出:http://ideone.com/aLNjv

    :text: sizeof(a)=5, strlen(a)=4
    :text: sizeof(b)=4, strlen(b)=4
    

    所以你的平台和/或编译器肯定有问题。由于您的 char 数组没有空终止符 (\0),因此可能是未定义的行为 (UB)。无论如何...

    虽然 a 和 b 可能看起来相同,但它们并不是由于您定义字符数组的方式。

    char a[] = "text";
    

    这个数组在内存中的样子如下:

    ----------------------
    | t | e | x | t | \0 |
    ----------------------
    

    双引号表示“文本字符串”,并会自动添加 \0(这就是大小为 5 的原因)。在 b 中,您必须手动添加它,但大小为 4。b 中的 strlen() 正在搜索直到您的实现结束,其中可能包含垃圾字符。在编码非空终止字符数组的许多安全方面,这是一个大问题。

    【讨论】:

    • 你在这里多次说“正确的输出”,但肯定strlen(b)是UB?
    • @0A0D:未定义的行为。你稍后含糊地提到它(在底部)。我想我的观点是,从技术上讲,strlen(b) 的任何答案都可能是“正确的”。
    • @Frexus:你没有展示你是如何推断 b 在 a 之前声明的,除了你有一个单词 text 的两倍。这肯定很奇怪,因为我展示的三个不同的编译器不会显示这种行为。由于“字符串”不是以 null 结尾的,因此任何事情都可能发生。
    • @0A0D,你为什么说平台或编译器有问题? OP 导致了未定义的行为——这当然不是他的工具的错。
    • 错,错,错。他们的编译器没有任何问题 - 在确定什么是正确的行为或其他方式时,您必须使用标准,而不是编译器。如果你从一个错误的前提开始,世界上所有的演绎推理都对你没有帮助。
    【解决方案4】:

    我使用 -S 标志在带有 GCC 的 Linux/x86 上编译了您的代码以查看汇编输出。这表明对我来说,b[] 分配在比 a[] 更高的内存地址,所以我没有得到 strlen(b)=4。

        .file   "str.c"
        .section    .rodata
        .align 4
    .LC0:
        .string ":%s: sizeof(a)=%d, strlen(a)=%d\n"
        .align 4
    .LC1:
        .string ":%s: sizeof(b)=%d, strlen(b)=%d\n"
        .text
    .globl main
        .type   main, @function
    main:
        pushl   %ebp
        movl    %esp, %ebp
        andl    $-16, %esp
        subl    $32, %esp
        movl    %gs:20, %eax
        movl    %eax, 28(%esp)
        xorl    %eax, %eax
        movl    $1954047348, 19(%esp)
        movb    $0, 23(%esp)
        movb    $116, 24(%esp)
        movb    $101, 25(%esp)
        movb    $120, 26(%esp)
        movb    $116, 27(%esp)
        leal    19(%esp), %eax
        movl    %eax, (%esp)
        call    strlen
        movl    %eax, %edx
        movl    $.LC0, %eax
        movl    %edx, 12(%esp)
        movl    $5, 8(%esp)
        leal    19(%esp), %edx
        movl    %edx, 4(%esp)
        movl    %eax, (%esp)
        call    printf
        leal    24(%esp), %eax
        movl    %eax, (%esp)
        call    strlen
        movl    $.LC1, %edx
        movl    %eax, 12(%esp)
        movl    $4, 8(%esp)
        leal    24(%esp), %eax
        movl    %eax, 4(%esp)
        movl    %edx, (%esp)
        call    printf
        movl    $0, %eax
        movl    28(%esp), %edx
        xorl    %gs:20, %edx
        je  .L2
        call    __stack_chk_fail
    .L2:
        leave
        ret
        .size   main, .-main
        .ident  "GCC: (Ubuntu/Linaro 4.5.2-8ubuntu4) 4.5.2"
        .section    .note.GNU-stack,"",@progbits
    

    在上面的代码中,$1954047348 后跟 $0 是带有空终止符的 []。之后的 4 个字节是 b[]。这意味着 b[] 在 a[] 之前被压入堆栈,因为堆栈在此编译器上向下增长。

    如果您使用 -S(或等效项)进行编译,您应该会看到 b[] 的地址低于 a[],因此您将得到 strlen(b)=8。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-02-07
      • 2013-04-15
      • 2022-01-17
      • 2020-11-28
      • 1970-01-01
      • 2016-03-25
      • 2015-06-25
      • 1970-01-01
      相关资源
      最近更新 更多