Будет ли std::string всегда заканчиваться нулем в C++11?
В публикации 2008 года на своем сайте Херб Саттер утверждает следующее:
Существует активное предложение еще более ужесточить это в C++0x и потребовать нулевого завершения и, возможно, запретить реализации копирования при записи по причинам, связанным с параллелизмом. Вот статья: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2534.html. Я думаю, что одно или оба предложения в этом документе, вероятно, будут приняты, но мы увидим на следующем заседании или два.
Я знаю, что C++11 теперь гарантирует, что содержимое std::string хранится непрерывно, но приняли ли они вышеупомянутое в окончательном варианте?
Будет ли теперь безопасно использовать что-то вроде &str[0]
?
3 ответа
Да. Согласно C++0x FDIS 21.4.7.1/1, std::basic_string::c_str()
должен вернуться
указатель
p
такой, чтоp + i == &operator[](i)
для каждогоi
в[0,size()]
,
Это означает, что данная строка s
, указатель возвращается s.c_str()
должен совпадать с адресом начального символа в строке (&s[0]
).
&str[0]
безопасно использовать - если вы не предполагаете, что он указывает на строку с завершающим нулем.
Начиная с C++11, требования включают (section [string.accessors]):
-
str.data()
а такжеstr.c_str()
указывают на строку с завершающим нулем. -
&str[i]
==str.data() + i
, для0 <= i <= str.size()
- обратите внимание, что это означает, что хранилище непрерывно.
Однако нет требования, чтобы
&str[0] + str.size()
указывает на нулевой терминатор.
Соответствующая реализация должна размещать нулевой ограничитель в хранилище непрерывно, когда
data()
, или же
operator[](str.size())
называются; но нет необходимости помещать его в какую-либо другую ситуацию, например, при вызове
operator[]
с другими аргументами.
Чтобы уберечь вас от чтения длинного обсуждения в чате ниже: было высказано возражение, что если
c_str()
было бы написать нулевой терминатор, это вызвало бы гонку данных в res.on.data.races#3 ; и я не был согласен с тем, что это будет гонка данных.
Хотя c_str() возвращает версию std :: string с завершающим нулем, при смешивании C ++ std :: string со строками C char * могут возникнуть сюрпризы.
Нулевые символы могут оказаться внутри C ++ std :: string, что может привести к незначительным ошибкам, поскольку функции C будут видеть более короткую строку.
Ошибочный код может перезаписать нулевой терминатор. Это приводит к неопределенному поведению. Функции C затем будут читать за пределами строкового буфера, что может вызвать сбой.
#include <string>
#include <iostream>
#include <cstdio>
#include <cstring>
int main()
{
std::string embedded_null = "hello\n";
embedded_null += '\0';
embedded_null += "world\n";
// C string functions finish early at embedded \0
std::cout << "C++ size: " << embedded_null.size()
<< " value: " << embedded_null;
printf("C strlen: %d value: %s\n",
strlen(embedded_null.c_str()),
embedded_null.c_str());
std::string missing_terminator(3, 'n');
missing_terminator[3] = 'a'; // BUG: Undefined behaviour
// C string functions read beyond buffer and may crash
std::cout << "C++ size: " << missing_terminator.size()
<< " value: " << missing_terminator << '\n';
printf("C strlen: %d value: %s\n",
strlen(missing_terminator.c_str()),
missing_terminator.c_str());
}
Выход:
$ c++ example.cpp
$ ./a.out
C++ size: 13 value: hello
world
C strlen: 6 value: hello
C++ size: 3 value: nnn
C strlen: 6 value: nnna�