【问题标题】:Memory usage of literal strings in CC中文字字符串的内存使用情况
【发布时间】:2014-07-31 20:47:48
【问题描述】:

当您将字符串 lteral 传递给参数中的函数而不是指向字符数组的指针时,编译器如何管理内存?

例子:

static const char myString[LENGTH] = "A string";
myFunction(myString);

和:

myFunction("A string");

通过指针传递静态常量(很可能存储在 ROM 中)是否会在 RAM 使用方面产生显着优势?

当传递字符串文字时,它是完全复制为sizeof(myString) 的局部变量,还是编译器“知道”通过引用传递它,因为数组在C 中总是通过引用传递?

【问题讨论】:

    标签: c string memory compiler-construction string-literals


    【解决方案1】:

    该标准没有规定如何使用字符串文字,或者即使在不同部分中使用的相同字符串文字是否会被共享。它只是说它们具有静态存储持续时间并且修改它们是未定义的行为。否则,字符串文字只是一个字符数组,并且会相应地表现。

    这在草案 C99 标准部分 6.4.5 中有介绍 字符串文字

    在翻译阶段 7,一个字节或零值代码被附加到每个多字节 由一个或多个字符串文字产生的字符序列。66) 多字节字符 然后使用序列来初始化静态存储持续时间和长度的数组 足以包含序列。对于字符串文字,数组元素有 输入字符,

    和:

    如果这些数组的元素具有 适当的值。如果程序试图修改这样的数组,行为是 未定义。

    在分配给myString 的情况下,它将被复制到为myString 分配的内存中,这在6.7.8 部分中进行了介绍初始化 说:

    字符类型的数组可以由字符串字面量初始化,可选 括在大括号中。字符串文字的连续字符(包括 如果有空间或数组大小未知,则终止空字符)初始化 数组的元素。

    【讨论】:

    • 在这种情况下,“复制”是什么意思?
    【解决方案2】:

    当传递字符串文字时,它是完全复制为 sizeof(myString) 的局部变量还是编译器“知道”通过引用传递它,因为在 C 中数组总是通过引用传递?

    字符串字面量存储为一个数组,这样它就可以在程序的生命周期内使用,并且与任何其他数组表达式一样遵循相同的转换规则;也就是说,除非它是 sizeof 或一元 & 运算符的操作数,或者是用于在声明中初始化数组的字符串文字,否则类型为“T 的 N 元素数组”的表达式将被转换(“衰减”)为“指向T”类型的表达式,表达式的值将是数组中第一个元素的地址。因此,在通话中

    myFunction( mystring );
    

    myFunction( "A string" );
    

    两个参数都是数组类型的表达式,sizeof 或一元 & 运算符的操作数都不是,因此在这两种情况下,表达式都衰减为指向第一个元素的指针。就函数调用而言,两者完全没有区别。

    让我们看一个真实的例子(SLES 10、x86_64、gcc 4.1.2)

    #include <stdio.h>
    
    void myFunction( const char *str )
    {
      printf( "str = %p\n", (void *) str );
      printf( "str = %s\n", str );
    }
    
    int main( void )
    {
      static const char mystring[] = "A string";
      myFunction( mystring );
      myFunction( "A string" );
    
      return 0;
    }
    

    myFunction 打印出字符串文字和mystring 变量的地址和内容。结果如下:

    [fbgo448@n9dvap997]~/prototypes/literal: gcc -o literal -std=c99 -pedantic -Wall -Werror literal.c
    [fbgo448@n9dvap997]~/prototypes/literal: ./literal
    str = 0x400654
    str = A string
    str = 0x40065d
    str = A string
    

    字符串文字和mystring 数组都存储在可执行文件的.rodata(只读)部分中:

    [fbgo448@n9dvap997]~/prototypes/literal: objdump -s literal
    ...
    Contents of section .rodata:
     40063c 01000200 73747220 3d202570 0a007374  ....str = %p..st
     40064c 72203d20 25730a00 41207374 72696e67  r = %s..A string
     40065c 00412073 7472696e 6700               .A string.
    ...
    

    mystring 声明中的static 关键字告诉编译器mystring 的内存应该在程序启动时留出并保留到程序终止。 const 关键字表示内存不应该被代码修改。在这种情况下,将其粘贴在 .rodata 部分非常有意义。

    这意味着在运行时不会为mystring 分配额外的内存;它已经作为图像的一部分分配。在这种特殊情况下,对于这个特殊的平台,使用其中一个或另一个之间没有绝对没有区别

    如果我mystring声明为static,如

    int main( void )
    {
      const char mystring[] = "A string";
      ...
    

    然后我们得到:

    str = 0x7fff2fe49110
    str = A string
    str = 0x400674
    str = A string
    

    意味着只有字符串文字被存储在.rodata

    Contents of section .rodata:
     40065c 01000200 73747220 3d202570 0a007374  ....str = %p..st
     40066c 72203d20 25730a00 41207374 72696e67  r = %s..A string
     40067c 00                                   .
    

    因为它被声明为main 的本地并且 声明为static,所以mystring 被分配了auto 存储持续时间;在这种情况下,这意味着内存将在运行时从堆栈中分配,并将在mystring 的封闭范围(即main 函数)的持续时间内保持。作为声明的一部分,字符串文字的内容将被复制到数组中。由于它是从堆栈分配的,因此数组原则上是可修改的,但 const 关键字告诉编译器拒绝任何试图修改它的代码。

    【讨论】:

      【解决方案3】:

      很可能在内存使用方面没有差异。

      在这两种情况下,字符串都将存储在某个静态位置,编译器将简单地以一种或另一种方式使用指向字符串的指针。对您而言,这意味着内存使用量完全相同。

      如果引用的相同字符串很少,第二种情况(文字字符串)可能更有效 - 编译器将只使用一个指针。第一种情况必须分配多个具有完全相同内容的内存位置。

      【讨论】:

        【解决方案4】:

        字符串文字在内存中的存储方式没有区别,无论您是否使用constantstatic 存储类修饰符,或者如果您将其用作函数参数而不是中间变量/指针:它们始终存储在 代码段 中。优化器还将用文字本身的地址替换对中间指针 MY_STRING 的引用。

        示例如下:

        Example:                       Allocation Type:   Read/Write:  Storage Location:
        ============================================================================
        const char* str = "Stack";     Static             Read-only    Code segment
        char* str = "Stack";           Static             Read-only    Code segment
        char* str = malloc(...);       Dynamic            Read-write   Heap
        char str[] = "Stack";          Static             Read-write   Stack
        char strGlobal[10] = "Global"; Static             Read-write   Data Segment (R/W)
        

        参考文献

        1. 声明的字符串和分配的字符串的区别,访问时间:2014-07-31,&lt;https://stackoverflow.com/questions/16021454/difference-between-declared-string-and-allocated-string&gt;

        【讨论】:

          【解决方案5】:

          在 C 中,数组确实是通过引用传递的。字符串字面量也不例外。

          【讨论】:

          • 不。数组的地址由 value 传递。 stackoverflow.com/questions/4774456/… 如果你修改作为正式函数参数传递给函数的参数,你实际上并没有修改参数,只是值的副本。因此,简而言之,数组不是通过引用传递的。对数组的引用按值传递。
          • @Dogbert:番茄,番茄。通过值传递地址通过引用传递数组。您可以更改数组值,这证明它是通过引用。
          • 你还是错了。 stackoverflow.com/questions/10240161/… 按值传递地址不等于按引用传递地址,因为前者只是将参数的副本压入堆栈,而后者允许直接操作参数。这是用于引用 C 的惰性语言,因为它实际上不允许像在 C++ 中那样通过引用传递。 “引用”的使用在两种语言之间并不相似。
          • @Dogbert:再读一遍。我写了“通过引用传递数组”。不是“通过引用传递地址”。通过引用传递地址将是int**
          • 但您缺少的更大问题是您无法在 C (stackoverflow.com/questions/2188991/…) 中通过引用传递数组。只是一个指向数组起始地址的指针。通过引用传递数组可以防止指针衰减(即:sizeof 仍然有效),并且这种功能在 C 中不存在。通过将数组作为参数传递,它衰减为按值传递地址。 “通过引用传递”的整个概念在 C 中没有位置,它只是懒惰的语言学。 stackoverflow.com/questions/1461432/what-is-array-decaying
          【解决方案6】:

          静态字符串将在 RAM 中存储一次。字符串文字将在 RAM 中存储一次。当传递给函数时,静态位置将被加载并作为参数传递,或者将传递文字位置。就存储而言没有区别。

          但是,将文字字符串放在一个位置并引用它们将比通过代码传播字符串文字更易于维护。

          【讨论】:

          • A const 静态字符串可能只存在于 ROM 中而不存在于 RAM 中 - 编译器/平台相关问题。
          猜你喜欢
          • 2019-03-21
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-12-24
          相关资源
          最近更新 更多