字符串初始化器会不会浪费内存?

时间:2016-02-17 06:51:42

标签: c arrays memory embedded language-lawyer

要初始化char数组,通常我会写:

char string[] = "some text";

但是今天,我的一个同学说应该使用:

char string[] = {'s', 'o', 'm', 'e', ' ', 't', 'e', 'x', 't', '\0'};

我告诉他放弃可读性和简洁性是疯狂的,但他认为用字符串初始化一个char数组实际上会创建两个字符串,一个在堆栈中,另一个在只读内存中。使用嵌入式设备时,这会导致内存中不可接受的浪费。

当然,字符串初始化器似乎更清晰,所以我将在我的程序中使用它们。但问题是,字符串初始化器会创建两个相同的字符串吗?或者字符串初始化器只是语法糖?

6 个答案:

答案 0 :(得分:6)

编辑后,两个定义之间没有区别。两者都将生成一个包含十个字符的数组,并初始化为相同的内容。

这实际上很容易验证:首先检查sizeof为您提供的两个数组,然后您可以使用例如memcmp比较两个数组。

第二次初始化几乎等于第一次初始化,只有一次关键区别:第二次数组没有以字符串形式终止。

第一个创建一个包含十个字符的数组(包括终结符),第二个创建一个包含九个字符的数组。如果你不使用数组作为字符串,那么你将在第二次初始化时保存一次元素。

答案 1 :(得分:6)

char string[] = "some text";

100%相当于

char string[] = {'s', 'o', 'm', 'e', ' ', 't', 'e', 'x', 't', '\0'};

你的朋友感到困惑:如果string是一个局部变量,那么在两个情况下你会创建两个字符串。存储在堆栈中的变量string和驻留在只读存储器(.rodata)中的只读字符串文字。

无法避免只读存储,因为必须在某处分配所有数据。您无法在空中分配字符串文字。您所能做的就是将它从一个只读存储器段移动到另一个只读存储器段,最终将为您提供相同的程序大小。

前一种风格一般是首选,因为它更具可读性。它确实是一种语法糖。

但它也是首选,因为它可以简化一些称为“字符串池”的编译器优化,它允许编译器以更加内存有效的方式存储字符串文字"some text"。如果逐个字符地初始化字符串,则编译可能会或可能不会意识到它是只读字符串常量。

答案 2 :(得分:3)

C标准有一个“特殊情况”,允许您使用字符串文字初始化数组:

  

§6.7.9/ 14字符数组可以由字符初始化   字符串文字或UTF-8字符串文字,可选择用大括号括起来。   字符串文字的连续字节(包括终止空值)   字符,如果有空间或数组的大小未知)   初始化数组的元素。

就是这样。它没有说任何其他内容,这将是平台和编译器的实现细节。与C ++明确给出字符串文字静态存储持续时间不同,C标准。这是隐含的。有一些常见的扩展允许您修改字符串文字,这意味着它不能保证它将被放在只读内存中。

答案 3 :(得分:3)

从语义上讲,两条线是相同的。但实际后果将取决于编译器。

尝试http://gcc.godbolt.org/显示了各种策略:

  • 使用一系列带有即时操作数的string result = Regex.Replace(text, @"([^\r])\n", "$1"); 指令(或等效指令)一次填充数组一个字符。

  • 使用movb对一次填充数组一个双字,其中第一个具有立即双字操作数。

  • 将数据从存储在movabsq / movq部分的字符串常量复制到数组中。

不同的编译器对这两种情况使用不同的策略。特别是,.rodata仅针对gcc的情况发现了movabsq优化,这使得您的朋友的策略有点笨重(因为生成的代码有更多字节)。

尝试不同的优化设置可能会产生更多变化。

很明显,基本数据必须存储在程序中的某处,无论是在数据部分还是在可执行代码中的一系列立即操作数。由于弄清楚或猜测特定样式如何影响给定编译器的优化能力是不切实际的,唯一合理的方法是使用最容易阅读和维护的样式。 (有用的推论是编译器可能也会使用最常见的样式进行最简单的时间。)

如果这实际上对性能至关重要,则必须检查所使用的实际编译器生成的代码。但是你首先应该问问自己是否真的需要初始化的可变缓冲区。

答案 4 :(得分:1)

  • 在第一种情况下,使用长度为10个字节的文字字符串常量初始化string[],它将在只读段中实例化。

  • 在第二种情况下,string[]使用长度为10个字节的常量字符常量数组进行初始化,该数组将在只读段中实例化。

两种情况在语义和内存要求上都是相同的。第一种只是第二种语法糖(更方便,更不容易出错)。

如果需要使用编译时常量数据初始化非只读数据,则无论使用何种语法,都必须编译常量初始化器。你无法得到任何东西。但是,如果数据是常量,则可以通过声明:

来使用单个只读副本
const char* string = "some text" ;

这将为常量字符串创建一个指针string,并且可以在与之相比时节省内存:

#define string "some text"

可能会生成多个副本"某些文字"在任何地方使用宏string。 (尽管大多数现代编译器/链接器工具链都能在任何情况下删除重复的字符串)。在第一个实例中,您可以获取string的地址,并确保所有引用的值都相同,而宏不同,每个引用都不会被优化。另一个语义差异是const char* stringsizeof(string)是指针的大小,string[]的时间是初始化的长度(包括nul终结符)

答案 5 :(得分:0)

  

字符串初始值设定项会创建两个相同的字符串吗?或者字符串初始化器只是语法糖?

这两个案例完全不同:

第一案:

char string[] = "some text";  // <-- string initialization

此语法是特定于字符串的,不能应用于任何其他数据类型。它会在末尾自动添加\0字符,以确保printf等库函数知道输出的结束位置(使用%s控制字符串)。

第二案:

char string[] = {'s', 'o', 'm', 'e', ' ', 't', 'e', 'x', 't'};  // <--  array initialization

此语法用于初始化数组但不是字符串。语法可用于初始化其他类型的数组(例如intlong等)。它永远不会在数组末尾自动添加\0。因此,使用printf控件对{char}数组进行%s错误。

简而言之,这些是用于不同目的的两种不同的初始化语法。如果您需要字符串,请使用第一种语法,如果您使用字符数组 - 则使用第二种语法。