检查相等的字符串文字是否存储在同一地址
Check whether equal string literals are stored at the same address
我正在开发一个使用无序容器的(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
- 为什么我可以将变量存储在不是其最小对齐方式的倍数的地址?
- 是否可以仅通过将分配的指针地址存储在C++中来分析内存?
- 为什么存储在数组中的地址值与其自己的地址相同,它应该指向某个不同的地址(&arr[0])?
- 为什么使用存储在虚拟方法表中的地址调用虚拟函数的函数会返回垃圾?
- 存储 std::list 元素的地址;内存
- 为什么这段代码给出错误......'a'是指针到字符指针,应该存储's'的地址,因为s是指向数组的第一个元素的指针
- 如何反转存储在内存地址的位?
- 为什么类型转换对象不会更改其地址?有关对象类型的信息存储在哪里?
- 为什么存储对象地址在缓冲区中会导致内存泄漏并删除它们
- 相同的地址存储在链表的头部
- 可以读/写EEPROM地址,但是只能读/写第一个存储页面
- 整数必须存储在连续的内存地址中吗?
- 替换WCHAR_T错误返回存储地址
- 更改存储在C 特定地址中的值
- C++为什么字符串的地址尽管是十六进制格式,但不能存储在 long int 变量中?
- 一系列指针存储地址并以后打印
- 为什么此指针包含存储在堆上的地址
- 如何在数组中存储地址 - C++
- 用于存储地址的单列列表的数据结构,更好地在C++中查找O(1)
- 在类数据结构中存储地址和路由信息