【问题标题】:How Should I Define/Declare String Constants我应该如何定义/声明字符串常量
【发布时间】:2019-03-04 01:47:06
【问题描述】:

我一直使用 C 中的字符串常量作为以下之一

char *filename = "foo.txt";
const char *s = "bar";    /* preferably this or the next one */
const char * const s3 = "baz":

但是,在阅读this 之后,现在我想知道,我是否应该将我的字符串常量声明为

const char s4[] = "bux";

?

请注意,建议为重复的链接问题是不同的,因为这个问题专门询问 constant 字符串。我知道类型有什么不同以及它们是如何存储的。该问题中的数组版本是 not const-qualified。这是一个简单的问题,即我是否应该将常量数组用于常量字符串与我一直使用的指针版本。这里的答案已经回答了我的问题,在 SO 和 Google 上搜索了两天并没有得到确切的答案。多亏了这些答案,我了解到当数组标记为 const 时,编译器可以做一些特殊的事情,并且确实(至少有一种)情况我现在将使用数组版本。

【问题讨论】:

  • 有趣.. 作者是对的——“数组”不同于“指针”。我会 THOUGHT 认为指针语法(例如const char *s = "bar";)通常是“首选”。我对他的结论感到惊讶,即“数组语法”实际上更有效——使用不同的编译器,在不同的平台上。
  • 确实如此。我总是尽可能使用const char *s = "bar";。我从来没有真正想过使用 const-char 数组。两天来我一直试图找到一个可靠的答案,所以我想我必须在这里问:P。我意识到我可以将sizeof 与数组版本一起使用,但这并不是很重要,至少在我目前的情况下。我想知道,根据作者的考虑,general 方法应该是什么。
  • 对我来说似乎是一个微优化。
  • 我有另一个我正在阅读的专业阵列版本链接,这是关于使用指针版本的开销。我现在似乎找不到它,但如果我找到了,我会回帖。还有@dbush,我敢肯定,但我不知道我是否患有强迫症或什么,但我对真正平凡的东西真的很挑剔哈哈。
  • 在带有 clang-1000.11.45.5 的 Apple LLVM 10,0.0 中,如果您在 const char *ptr = "Lorum ipsum"; 中的 * 之后插入 const,差异就会消失。编译器必须加载 ptr 的事实完全是因为它可以在编译器不可见的其他模块中进行更改。使指针const 消除了这一点,编译器可以直接准备字符串的地址,而无需加载指针。

标签: c


【解决方案1】:

指针和数组是不同的。将字符串常量定义为指针或数组适合不同的用途。

当你定义一个不会改变的全局字符串常量时,我​​建议你将它设为一个 const 数组:

const char product_name[] = "The program version 3";

将其定义为const char *product_name = "The program version 3";实际上定义了2个对象:字符串常量本身,它将驻留在一个常量段中,以及可以更改为指向另一个字符串或设置为NULL的指针。

相反,将字符串常量定义为局部变量最好是使用const char * 类型的局部指针变量,并使用字符串常量的地址进行初始化:

int main() {
    const char *s1 = "world";
    printf("Hello %s\n", s1);
    return 0;
}

如果将此定义为数组,则根据编译器和函数内部的用法,代码将在堆栈上为数组腾出空间并通过将字符串常量复制到其中来对其进行初始化,长时间操作的成本更高字符串。

还要注意const char const *s3 = "baz";const char *s3 = "baz"; 的冗余形式。它与const char * const s3 = "baz"; 不同,后者定义了一个指向常量字符数组的常量指针。

最后,字符串常量是不可变的,因此应该具有const char [] 类型。 C 标准有意允许程序员将其地址存储到非 const 指针中,如 char *s2 = "hello"; 中那样,以避免对遗留代码产生警告。在新代码中,强烈建议始终使用const char * 指针来操作字符串常量。当函数不更改字符串内容时,这可能会强制您将函数参数声明为 const char *。这个过程被称为constification,可以避免细微的错误。

请注意,某些函数违反了 const 传播:strchr() 不会修改接收到的字符串,声明为 const char *,但会返回 char *。因此,可以通过这种方式将指向字符串常量的指针存储到普通的 char * 指针中:

char *p = strchr("Hello World\n", 'H');

这个问题在 C++ 中通过重载解决。 C 程序员必须将此视为一个缺点。更烦人的情况是 strtol() 的情况,其中传递了 char * 的地址,并且需要强制转换以保持适当的 constness。

【讨论】:

  • 对不起,const char *const s3 是我的意思。现在修复它:)。那么对于全局来说,使用数组版本不会产生副本?
  • s1 的情况下你会得到一个“冗余”指针变量,你不是很容易在@ 中出现一个“冗余”数组副本 987654343@ case(对他们来说是语义)哪个更糟?我之前从未听过这个建议。
  • @LightnessRacesinOrbit:在全局变量的情况下,运行时没有副本,但实际上字符串常量可能在二进制文件和内存中重复:const char *s1 = "toto", *s2 = "toto"; 可能会同时初始化s1s2 为相同的值,而 const char s1[] = "toto", s2[] = "toto"; 定义了 2 个单独的对象,每个对象具有相同的重复内容。
  • 是的,这是否表明const char* 是一个更好的选择?
  • 我已将此标记为已接受的答案,因为这更直接地回答了我的“我应该如何声明”问题,尽管@EricPostpischil 的答案中的信息直接与我的源链接中提供的信息相匹配。不幸的是,我不能接受两个答案,但我敦促所有正在阅读此答案的人也查看他的答案。谢谢大家!
【解决方案2】:

链接的文章探讨了一个小的人为情况,如果您在const char *ptr = "Lorum ipsum"; 中的* 之后插入const,差异就会消失(在Apple LLVM 10.0.0 中使用clang-1000.11.45.5 进行测试)。

编译器必须加载ptr 的事实完全是因为它可以在编译器不可见的其他模块中进行更改。使指针const 消除了这一点,编译器可以直接准备字符串的地址,而无需加载指针。

如果你要声明一个指向字符串的指针并且从不改变指针,那么将它声明为static const char * const ptr = "string";,编译器可以很高兴地在使用ptr的值时提供字符串的地址。它不需要从内存中实际加载ptr 的内容,因为它永远不会改变,并且会指向编译器选择存储字符串的位置。这与static const char array[] = "string"; 相同——只要需要数组的地址,编译器就可以根据它选择存储数组的位置来提供它。

此外,使用 static 说明符,ptr 在翻译单元(正在编译的文件)之外无法知道,因此编译器可以在优化期间将其删除(只要您没有获取它的地址,也许当将其传递给翻译单元之外的另一个例程)。结果应该是指针方法和数组方法没有区别。

经验法则:尽可能多地告诉编译器:如果它永远不会改变,请将其标记为const。如果它是当前模块的本地,则将其标记为static。编译器拥有的信息越多,它可以优化的越多。

【讨论】:

  • static 只对全局范围或main() 内部有帮助,但对任何其他函数范围没有帮助?还是应该在main() 中使用static?如果,例如我正在使用字符串常量作为文件名,并且我要调用自己的函数来打开该文件,即使该函数在另一个文件中,它是否是静态的也没关系(除非我传递了它的地址) ...还是我把它混在一起了。老实说,我从来没有真正使用过static
  • @RastaJedi:在文件范围内(在任何函数之外),static 本质上只是改变了在当前翻译单元之外是否可以知道该事物。在一个块内(函数中大括号中的任何一组语句,main 或其他),static 都表示“此名称在此块之外是未知的”和“这个东西在所有程序执行中都存在,而不仅仅是阻止执行。”但是,对于 const 对象,两个生命周期(静态和自动)通常优化为同一件事。 (出于语义原因,获取事物的地址可以改变这一点,但简单的使用不受影响。)
  • 因此,如果我有一个 static 字符串常量,并将其传递给另一个 TLU 中的例程,这仍然可以工作,但优化的可能性消失了,你的意思是什么?在一个函数中,如果我不需要在其他任何地方更改它或将它传递给任何其他例程,请尝试将其标记为static,以便进行优化?非恒定值的情况如何。如果我需要在超出范围或限制在文件范围内的 TLU 时保留它的价值,只需使用 static?我假设如果它是可修改的,就不可能以这种方式进行优化。
  • 在函数级别给定static const char * const ptr = "string";,将ptr 传递给当前翻译单元之外的例程应该与传递"string"array 具有相同的效果,其中array 是@987654351 @。在所有这些情况下,编译器只需要传递实际字符串的地址,它本身可能不会改变。所以它应该为所有这些生成相同的代码。如果您在模块外部传递了&ptr,那么编译器必须创建一个实际的ptr,以便它可以获取它的地址。但这是一个不同的用例。
  • 但 gcc 和 icc 在指针与数组中给出的结果并不相同,即使您将指针设为 * const。但是总体而言,这种情况是“未成熟的优化”。
【解决方案3】:

从性能的角度来看,这是一个相当小的优化,对于需要以尽可能低的延迟运行的低级代码来说是有意义的。

但是,我认为const char s3[] = "bux"; 从语义角度来看更好,因为右侧的类型更接近左侧的类型。出于这个原因,我认为用数组语法声明字符串常量是有意义的。

【讨论】:

  • C 中的字符串文字在技术上不是const-qualified 类型吗?尽管是一成不变的。或者您可能只是指它的数组方面。数组版本也不必复制整个字符串吗?
  • 你是对的。但是,修改字符串文字的元素是UB,我指的是数组部分。 :)
猜你喜欢
  • 1970-01-01
  • 2010-11-28
  • 2015-05-21
  • 2014-02-14
  • 2011-08-12
  • 2014-03-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多