【问题标题】:Is storage for the same content string literals guaranteed to be the same?相同内容字符串文字的存储是否保证相同?
【发布时间】:2019-02-24 16:32:57
【问题描述】:

下面的代码安全吗?编写类似这样的代码可能很诱人:

#include <map>

const std::map<const char*, int> m = {
    {"text1", 1},
    {"text2", 2}
};

int main () {
    volatile const auto a = m.at("text1");
    return 0;
}

该映射仅用于字符串字面量。

我认为这是完全合法的并且似乎可以正常工作,但是我从未见过保证在两个不同地方使用的文字指针相同。我无法让编译器为具有相同内容的文字生成两个单独的指针,所以我开始怀疑这个假设有多可靠。

我只对具有相同内容的文字是否可以有不同的指针感兴趣。或者更正式地说,上面的代码可以除外吗?

我知道有一种方法可以编写代码以确保其正常工作,并且我认为上述方法很危险,因为编译器可能会决定为文字分配两个不同的存储空间,尤其是当它们被放置在不同的翻译单元中时。我说的对吗?

【问题讨论】:

  • “但是我从来没有看到保证在两个不同的地方使用的字面量的指针是相同的” - 这是一个很好的理由
  • 为什么不使用std::string
  • @tkausl 因为这超出了明确提到的问题范围。我知道如何正确地写它。另外,因为std::string ctor 不是constexpr
  • @StoryTeller 这是在开玩笑。请分享!
  • @luk32 作为旁注,您的代码是完全合法的,它可能无法达到您的预期(即,std::terminate 可能由于未捕获的异常而被调用)。

标签: c++ storage language-lawyer string-literals


【解决方案1】:

具有完全相同内容的两个字符串文字是否是完全相同的对象,未指定,我认为最好不要依赖。引用标准:

[lex.string]

16 评估字符串文字会产生字符串文字对象 具有静态存储持续时间,从给定字符初始化为 上面指定。是否所有字符串文字都是不同的(即, 存储在不重叠的对象中)以及是否连续 对字符串文字的评估产生相同或不同的对象 未指定。

如果您希望避免 std::string 的开销,您可以编写一个简单的视图类型(或在 C++17 中使用 std::string_view),它是字符串文字的引用类型。使用它来进行智能比较,而不是依赖文字身份。

【讨论】:

  • 在生产中,我确实使用了const char* 周围的自定义包装器类型并重载了相关运算符。我想知道我是否写了多余的代码。在我的特殊情况下,字符串开销不是什么大问题,大约是constexpr ctor,我仍然仅限于 c++14。
  • @luk32 - 您可以明确地编写一个针对字符串文字的包装器(使用 char* 需要进行长度检查)。请考虑使用this for example。它基本上消除了所有开销,除了比较本身。
  • @AndreasRejbrand:绝对!
  • @StoryTeller 我做了类似的事情,抛光后几乎相同,但没有 constexpr 大小(我对 c'tor 不够聪明)。够有趣的。它以某种方式从编译时 bimap 查找中减少了两条 asm 指令:godbolt.org/z/ZCMUKv。我认为这与出于某种原因传递大小有关。
  • @luk32 - kewl! :) 我怀疑您减小的对象大小在其中起作用。
【解决方案2】:

标准不保证内容相同的字符串字面量地址相同。事实上,[lex.string]/16 说:

是否所有字符串文字都是不同的(即,存储在不重叠的对象中)以及 string-literal 的连续评估是否产生相同或不同的对象是未指定的。

第二部分甚至说当一个包含字符串文字的函数被第二次调用时,你可能不会得到相同的地址!虽然我从未见过编译器这样做。

因此,在重复字符串文字时使用相同的字符数组对象是一种可选的编译器优化。通过安装 g++ 和默认编译器标志,我还发现我在同一个翻译单元中为两个相同的字符串文字获得了相同的地址。但正如你猜到的,如果相同的字符串文字内容出现在不同的翻译单元中,我会得到不同的结果。


一个相关的有趣点:也允许不同的字符串文字使用重叠的数组。也就是说,给定

const char* abcdef = "abcdef";
const char* def = "def";
const char* def0gh = "def\0gh";

您可能会发现abcdef+3defdef0gh 都是同一个指针。

此外,关于重用或重叠字符串字面量对象的这条规则仅适用于与字面量直接关联的未命名数组对象,如果字面量立即衰减为指针或绑定到数组的引用,则使用此规则。字面量也可用于初始化命名数组,如

const char a1[] = "XYZ";
const char a2[] = "XYZ";
const char a3[] = "Z";

这里数组对象a1a2a3是使用字面量初始化的,但被认为与实际字面量存储不同(如果这种存储存在的话)并遵循普通对象规则,所以存储因为这些数组不会重叠。

【讨论】:

  • 我只是在想如何编造一些病态的文字。一个简单的\0 就可以了,+1。
  • 如果您的示例使用const char *abcdef = "abcdef"; 等,那么优化将是合法的,但代码将被允许比较defdef0gh 的地址,并且标准指定它们将是可观察的不同。
  • @supercat 哎呀,这绝对正确。已修复,谢谢。
  • @aschepler 我建议将修复之前的注释和代码合并到这个答案中,恕我直言,这是一个有趣的技术性。我接受这个作为对较低分数的安慰(尽管实际上是在时间上正面交锋),更重要的是为了给出漂亮漂亮的反例。
  • 当然,在另一个带有命名数组的示例中再次添加,并解释了为什么行为不同。
【解决方案3】:

不,C++ 标准没有做出这样的保证。

也就是说,如果代码在同一个翻译单元中,那么很难找到反例。如果main() 是不同的翻译,那么反例可能更容易产生。

如果地图位于不同的动态链接库或共享对象中,则几乎可以肯定不是这种情况。

volatile 限定词是一个红鲱鱼。

【讨论】:

  • 我使用volatile 来实现在线编译器的复制粘贴友好性,以防止完全删除代码。我认为它确实以这种方式工作并且不会妨碍。
【解决方案4】:

C++ 标准不需要实现去重复字符串文字。

当字符串文字驻留在另一个翻译单元或另一个共享库中时,需要链接器 (ld) 或运行时链接器 (ld.so) 执行字符串文字重复数据删除。他们没有。

【讨论】:

    猜你喜欢
    • 2014-07-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-03
    • 1970-01-01
    • 2015-02-07
    • 2011-10-07
    相关资源
    最近更新 更多