在 C++11 中,我们知道
std::string
保证是连续的并且以空字符结尾(或者更迂腐地,以 charT()
结尾,在 char
的情况下是空字符 0)。
我需要使用这个 C API,它通过指针填充字符串。它写入整个字符串+空终止符。在 C++03 中,我总是被迫使用
vector<char>
,因为我无法假设 string
是连续的或空终止的。但在 C++11 中(假设有一个正确符合标准的 basic_string
类,这在某些标准库中仍然不确定),我可以。
或者我可以吗?当我这样做时:
std::string str(length);
该字符串将分配
length+1
个字节,最后一个字节由空终止符填充。那挺好的。但是当我将其传递给 C API 时,它将写入 length+1
字符。它将覆盖空终止符。
无可否认,它将用空字符覆盖空终止符。这很有可能会起作用(事实上,我无法想象它如何不能起作用)。
但我并不关心什么“有效”。我想知道,根据规范,用空字符覆盖空终止符是否可以?
不幸的是,这是UB,如果我对措辞的解释正确的话(无论如何,这是不允许的):
§21.4.5 [string.access] p2
返回:
(如果为*(begin() + pos)
),否则为对pos < size()
类型且值为T
的对象的引用; 参考值不得修改。charT()
(编辑错误,它说
T
而不是charT
。)
.data()
和.c_str()
基本上指向operator[]
(§21.4.7.1 [string.accessors] p1
):
返回: 一个指针
,使得p
对于p + i == &operator[](i)
中的每个i
。[0,size()]
LWG 2475 通过编辑
operator[](size())
的规范使其有效(以粗体插入文本):
否则,返回对具有值的
类型对象的引用charT
,将对象 修改为charT()
以外的任何值 导致未定义的行为。charT()
根据规范,覆盖终止
NUL
应该是未定义的行为。
因此,正确的做法是在字符串中分配 length+1
个字符,将字符串缓冲区传递给 C API,然后 resize()
返回到 length
:
// "+ 1" to make room for the terminating NUL for the C API
std::string str(length + 1);
// Call the C API passing &str[0] to safely write to the string buffer
...
// Resize back to length
str.resize(length);
(FWIW,我在 MSVC10 上尝试了“覆盖 NUL”方法,效果很好。)
编辑 2024-FEB-27:自 2012 年(最初在此提出并回答此问题的年份)以来,C++ 标准已被修改,并且 自 C++17 起,覆盖
std::string
是合法的NUL
终结者与另一个 NUL
。
我想 n3092 不再是最新的,但这就是我所拥有的。第 21.4.5 节允许访问单个元素。需要 pos <= size(). If pos < size() then you get the actual element, otherwise (i.e. if pos == size()) then you get a non-modifiable reference.
我认为就编程语言而言,即使新值与旧值相同,一种可以修改值的访问也被视为修改。
g++ 有一个可以链接的迂腐库吗?