静态字符串字面量的内存分配

3

考虑以下结构:

struct example_t {
char * a;
char * b;
};

struct example_t test {
"Chocolate",
"Cookies"
};

我知道char*的内存分配是与实现相关的,但字符串字面值呢?

在这种情况下,C标准是否保证了"Chocolate"和"Cookies"相邻的位置?

在我测试的大多数实现中,这两个字面值没有填充,并且直接相邻。

这使得结构体可以通过memcpy快速复制,尽管我怀疑这种行为是未定义的。有人对此有什么信息吗?


4
如果这两个字符串在相邻的内存中,也不会改变结果。memcpy 只会复制指针而不是字符串本身。 - Weather Vane
显然,尽管您的评论可能对那些没有意识到这一点的人有用。我提到的memcpy将在第一个指针的位置执行。 - Don Scott
1
在尝试使用memcpy进行“快速复制”之前,请阅读C语言中的memcpy与赋值运算符 - Bo Persson
@BoPersson 非常好的观点。 寓意:不要因为性能而偏爱memcpy而不是赋值,_但是_这样做可能更方便。 (即,如果此结构体有4个或更多字符串,则编写一行代码可能在编程上更简单,而不是使用循环。) - Don Scott
@DonScott - 但你不需要循环或多行代码,你只需将一个结构体分配给另一个结构体,让编译器自己解决如何实现。 - Bo Persson
显示剩余2条评论
4个回答

4
在您的示例中,不存在两个字符串文字相对于彼此的位置的绝对保证。 GCC在这种情况下恰好展示了这种行为,但它没有展示这种行为的义务。
在这个示例中,我们看不到填充,甚至可以使用未定义的行为来演示字符串字面值的相邻性。这适用于GCC,但是使用备用libc或不同的编译器,您可能会获得其他行为,例如在翻译单元之间检测重复的字符串文字并减少冗余以节省最终应用程序的内存。
此外,尽管您声明的指针是char *类型,但实际上这些文字应该是const char*,因为它们将被存储在RODATA 中,并且写入该内存将导致段错误。

代码清单


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

struct example_t {
char * a;
char * b;
char * c;
};


int main(void) {

    struct example_t test = {
        "Chocolate",
        "Cookies",
        "And milk"
    };
    size_t len = strlen(test.a) + strlen(test.b) + strlen(test.c) + ((3-1) * sizeof(char));

    char* t= test.a;
    int i;
    for (i = 0; i< len; i++) {
        printf("%c", t[i]);
    }

    return 0;
}

样例输出

./a.out 
ChocolateCookiesAnd milk

gcc -S的输出


    .file   "test.c"
    .section    .rodata
.LC0:
    .string "Chocolate"
.LC1:
    .string "Cookies"
.LC2:
    .string "And milk"
    .text
    .globl  main
    .type   main, @function
main:
.LFB0:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    pushq   %rbx
    subq    $72, %rsp
    .cfi_offset 3, -24
    movq    $.LC0, -48(%rbp)
    movq    $.LC1, -40(%rbp)
    movq    $.LC2, -32(%rbp)
    movq    -48(%rbp), %rax
    movq    %rax, %rdi
    call    strlen
    movq    %rax, %rbx
    movq    -40(%rbp), %rax
    movq    %rax, %rdi
    call    strlen
    addq    %rax, %rbx
    movq    -32(%rbp), %rax
    movq    %rax, %rdi
    call    strlen
    addq    %rbx, %rax
    addq    $2, %rax
    movq    %rax, -64(%rbp)
    movq    -48(%rbp), %rax
    movq    %rax, -56(%rbp)
    movl    $0, -68(%rbp)
    jmp .L2
.L3:
    movl    -68(%rbp), %eax
    movslq  %eax, %rdx
    movq    -56(%rbp), %rax
    addq    %rdx, %rax
    movzbl  (%rax), %eax
    movsbl  %al, %eax
    movl    %eax, %edi
    call    putchar
    addl    $1, -68(%rbp)
.L2:
    movl    -68(%rbp), %eax
    cltq
    cmpq    -64(%rbp), %rax
    jb  .L3
    movl    $0, %eax
    addq    $72, %rsp
    popq    %rbx
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE0:
    .size   main, .-main
    .ident  "GCC: (Ubuntu 4.8.4-2ubuntu1~14.04) 4.8.4"
    .section    .note.GNU-stack,"",@progbits

1

不,相邻放置没有保证。

实际编译器将它们放得很远的一个场合是,如果相同的字符串字面量在不同的位置(作为只读对象)出现,并且启用了字符串合并优化。

例如:

 char *foo = "foo";
 char *baz = "baz";
 struct example_t bar = {
     "foo",
     "bar"
 }

可能最终在内存中的顺序是"foo",后跟"baz",然后是"bar"


1
这是一个示例,演示了一个实际场景,其中字符串不是相邻的。GCC决定重用先前使用过的字符串"Chocolate"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

const char *a = "Chocolate";
const char *b = "Spinach";

struct test_t {
    const char *a;
    const char *b;
};

struct test_t test = {"Chocolate", "Cookies"};

int main(void)
{
    printf("%p %p\n", (const void *) a, (const void *) b);
    printf("%p %p\n", (const void *) test.a, (const void *) test.b);
    return EXIT_SUCCESS;
}

输出:

0x400614 0x40061e
0x400614 0x400626

0

我将尝试向您展示gcc行为的示例,即使在这种情况下,您也无法在内存中对齐字符串:

#include <stdio.h>
#include <stdlib.h>

char *s = "Cookies";

struct test {
    char *a, *b, *c, *d;
};

struct test t = {
    "Chocolate",
    "Cookies",
    "Milk",
    "Cookies",
};

#define D(x) __FILE__":%d:%s: " x, __LINE__, __func__

#define P(x) do{\
    printf(D(#x " = [%#p] \"%s\"\n"), x, x); \
} while(0)

int main()
{
    P(t.a);
    P(t.b);
    P(t.c);
    P(t.d);
    return 0;
}

在这种情况下,由于编译器试图重用已经看到的字符串字面量,你用来分配给结构字段的字符串不会被对齐。
以下是程序的输出:
$ pru3
pru3.c:25:main: t.a = [0x8518] "Chocolate"
pru3.c:26:main: t.b = [0x8510] "Cookies"
pru3.c:27:main: t.c = [0x8524] "Milk"
pru3.c:28:main: t.d = [0x8510] "Cookies"

正如您所看到的,指针甚至为"Cookies"值重复了。

这里使用默认值进行编译,包括:

gcc -o pru3 pru3.c

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接