检查相等的字符串文字是否存储在同一地址

Check whether equal string literals are stored at the same address

本文关键字:存储 地址 是否 文字 字符串 检查      更新时间:2023-10-16

我正在开发一个使用无序容器的(C++)库。它们需要一个散列器(通常是模板结构std::hash的专门化)来处理它们存储的元素的类型。在我的例子中,这些元素是封装字符串文字的类,类似于本页底部示例的conststr。STL为常量字符指针提供了专门化,但是,它只计算指针,如"Notes"部分所述:

C字符串没有专门化。std::hash<const char*>生成指针值(内存地址)的散列不检查任何字符数组的内容。

尽管这很快(或者我认为是这样),但C++标准并不能保证几个相等的字符串文字是否存储在同一地址,正如本问题中所解释的那样。如果他们不这样做,散列的第一个条件就不会满足:

对于相等的两个参数k1和k2,std::hash<Key>()(k1) == std::hash<Key>()(k2)

如果给出了上述保证,或者其他一些算法,我希望使用所提供的专门化来选择性地计算哈希。尽管重新要求那些包含我的头或构建我的库的人定义一个特定的宏是可行的,但最好是由实现定义的宏。

在任何C++实现中,有没有宏,但主要是g++和clang,它们的定义保证了几个相等的字符串文本存储在同一地址?

一个例子:

#ifdef __GXX_SAME_STRING_LITERALS_SAME_ADDRESS__
const char str1[] = "abc";
const char str2[] = "abc";
assert( str1 == str2 );
#endif

在任何C++实现中,有没有宏,但主要是g++和clang,它们的定义保证了几个相等的字符串文本存储在同一地址?

  • gcc具有-fmerge-constants选项(这不是保证):

尝试跨编译单元合并相同的常量(字符串常量和浮点常量)。

如果汇编程序和链接器支持此选项,则此选项是优化编译的默认选项。请使用-fno merge常量来禁止此行为。

在-O、-O2、-O3、-Os级别启用。

  • Visual Studio具有字符串池(/GF选项:"消除重复字符串")

字符串池允许原本指向多个缓冲区的多个指针变成指向单个缓冲区的多重指针。在下面的代码中,s和t使用相同的字符串进行初始化。字符串池使它们指向相同的内存:

char *s = "This is a character buffer";
char *t = "This is a character buffer";

注意:虽然MSDN使用char*字符串文字,但应该使用const char*

  • clang显然也有-fmerge-constants选项,但除了--help部分之外,我找不到太多关于它的信息,所以我不确定它是否真的相当于gcc的选项:

不允许合并常数


无论如何,字符串文字的存储方式取决于实现(许多确实将它们存储在程序的只读部分)。

我只能建议使用std::string而不是C风格的字符串:它们的行为将完全符合您的预期。

您可以使用emplace()方法在容器中构建std::string

    std::unordered_set<std::string> my_set;
    my_set.emplace("Hello");

尽管C++似乎不允许以任何方式处理字符串文字,但如果您不介意将字符串文字重写为字符序列,有一种丑陋但可行的方法可以解决这个问题。

template <typename T, T...values>
struct static_array {
  static constexpr T array[sizeof...(values)] { values... };
};
template <typename T, T...values>
constexpr T static_array<T, values...>::array[];
template <char...values>
using str = static_array<char, values..., ''>;
int main() {
  return str<'a','b','c'>::array != str<'a','b','c'>::array;
}

这是返回零所必需的。编译器必须确保,即使多个转换单元实例化str<'a','b','c'>,这些定义也会被合并,并且最终只能得到一个数组。

不过,您需要确保不要将其与字符串文字混合使用。任何字符串文字都保证而不是与任何模板实例化的数组进行比较。

tacklelib C++11库有一个带有tmpl_string类的宏,用于保存文本字符串作为模板类实例。tmpl_string包含一个具有相同内容的静态字符串,它保证了相同模板类实例的相同地址。

https://github.com/andry81/tacklelib/blob/master/include/tacklelib/tackle/tmpl_string.hpp

测试:

https://github.com/andry81/tacklelib/blob/master/src/tests/unit/test_tmpl_string.cpp

示例:

const auto s = TACKLE_TMPL_STRING(0, "my literl string")

我在另一个宏中使用了它,以方便且一致地提取文字字符串begin/end:

#include <tacklelib/tackle/tmpl_string.hpp>
#include <tacklelib/utility/string_identity.hpp>
//...
std::vector<char> xml_arr;
xml_arr.insert(xml_arr.end(), UTILITY_LITERAL_STRING_WITH_BEGINEND_TUPLE("<?xml version='1.0' encoding='UTF-8'?>n"));

https://github.com/andry81/tacklelib/blob/master/include/tacklelib/utility/string_identity.hpp