Como você constrói um std :: string com um nulo incorporado?


89

Se eu quiser construir um std :: string com uma linha como:

std::string my_string("a\0b");

Onde eu quero ter três caracteres na string resultante (a, null, b), eu só consigo um. Qual é a sintaxe adequada?


4
Você terá que ter cuidado com isso. Se você substituir 'b' por qualquer caractere numérico, você criará silenciosamente a string errada. Veja: stackoverflow.com/questions/10220401/…
David Stone

Respostas:


129

Desde C ++ 14

temos sido capazes de criar std::string

#include <iostream>
#include <string>

int main()
{
    using namespace std::string_literals;

    std::string s = "pl-\0-op"s;    // <- Notice the "s" at the end
                                    // This is a std::string literal not
                                    // a C-String literal.
    std::cout << s << "\n";
}

Antes do C ++ 14

O problema é o std::stringconstrutor que const char*assume que a entrada é uma string C. As strings C são \0encerradas e, portanto, a análise para quando atinge o \0caractere.

Para compensar isso, você precisa usar o construtor que constrói a string a partir de uma matriz char (não uma string C). Isso leva dois parâmetros - um ponteiro para a matriz e um comprimento:

std::string   x("pq\0rs");   // Two characters because input assumed to be C-String
std::string   x("pq\0rs",5); // 5 Characters as the input is now a char array with 5 characters.

Nota: C ++ NÃOstd::string é terminado (como sugerido em outras postagens). No entanto, você pode extrair um ponteiro para um buffer interno que contém uma C-String com o método . \0c_str()

Verifique também a resposta de Doug T abaixo sobre o uso de a vector<char>.

Verifique também o RiaD para uma solução C ++ 14.


8
atualização: a partir de c ++ 11, as strings têm terminação nula. Dito isso, a postagem de Loki continua válida.
matthewaveryusa

14
@mna: Eles têm terminação nula em termos de armazenamento, mas não no sentido de que são terminados em nulo com terminação nula significativa (ou seja, com semântica de definição de comprimento de string), que é o significado usual do termo.
Lightness Races in Orbit

Bem explicado. Obrigado.
Joma

22

Se você estiver fazendo a manipulação como faria com uma string de estilo C (matriz de caracteres), considere usar

std::vector<char>

Você tem mais liberdade para tratá-lo como um array da mesma maneira que trataria uma string C. Você pode usar copy () para copiar em uma string:

std::vector<char> vec(100)
strncpy(&vec[0], "blah blah blah", 100);
std::string vecAsStr( vec.begin(), vec.end());

e você pode usá-lo em muitos dos mesmos lugares onde você pode usar strings C

printf("%s" &vec[0])
vec[10] = '\0';
vec[11] = 'b';

Naturalmente, no entanto, você sofre dos mesmos problemas que as cordas C. Você pode esquecer seu terminal nulo ou escrever além do espaço alocado.


Se você está tentando codificar bytes em string (bytes grpc são armazenados como string), use o método vetorial conforme especificado na resposta; não da maneira usual (veja abaixo) que NÃO construirá a string inteira byte *bytes = new byte[dataSize]; std::memcpy(bytes, image.data, dataSize * sizeof(byte)); std::string test(reinterpret_cast<char *>(bytes)); std::cout << "Encoded String length " << test.length() << std::endl;
Alex Punnen

13

Não tenho ideia de por que você deseja fazer tal coisa, mas tente isto:

std::string my_string("a\0b", 3);

1
Quais são suas preocupações por fazer isso? Você está questionando a necessidade de armazenar "a \ 0b" sempre? ou questionando o uso de um std :: string para tal armazenamento? Nesse último caso, o que você sugere como alternativa?
Anthony Cramp,

3
@Constantin então você está fazendo algo errado se estiver armazenando dados binários como uma string. É para isso que vector<unsigned char>ou unsigned char *foram inventados.
Mahmoud Al-Qudsi

2
Eu me deparei com isso ao tentar aprender mais sobre segurança de strings. Eu queria testar meu código para ter certeza de que ele ainda funciona, mesmo que leia um caractere nulo durante a leitura de um arquivo / rede o que espera ser dados textuais. Eu uso std::stringpara indicar que os dados devem ser considerados como texto simples, mas estou fazendo um trabalho de hash e quero ter certeza de que tudo ainda funciona com caracteres nulos envolvidos. Isso parece um uso válido de um literal de string com um caractere nulo incorporado.
David Stone,

3
@DuckMaestro Não, isso não é verdade. Um \0byte em uma string UTF-8 só pode ser NUL. Um caractere codificado multibyte nunca conterá - \0nem qualquer outro caractere ASCII.
John Kugelman

1
Eu me deparei com isso ao tentar provocar um algoritmo em um caso de teste. Portanto, existem razões válidas; embora poucos.
namezero

12

Quais novos recursos os literais definidos pelo usuário adicionam ao C ++? apresenta uma resposta elegante: Defina

std::string operator "" _s(const char* str, size_t n) 
{ 
    return std::string(str, n); 
}

então você pode criar sua string desta forma:

std::string my_string("a\0b"_s);

ou mesmo assim:

auto my_string = "a\0b"_s;

Existe um jeito "antigo":

#define S(s) s, sizeof s - 1 // trailing NUL does not belong to the string

então você pode definir

std::string my_string(S("a\0b"));

8

O seguinte vai funcionar ...

std::string s;
s.push_back('a');
s.push_back('\0');
s.push_back('b');

Você tem que usar parênteses em vez de colchetes.
jk.

5

Você terá que ter cuidado com isso. Se você substituir 'b' por qualquer caractere numérico, você criará silenciosamente a string errada usando a maioria dos métodos. Consulte: Regras para caracteres de escape de literais de string C ++ .

Por exemplo, deixei cair este trecho de aparência inocente no meio de um programa

// Create '\0' followed by '0' 40 times ;)
std::string str("\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00", 80);
std::cerr << "Entering loop.\n";
for (char & c : str) {
    std::cerr << c;
    // 'Q' is way cooler than '\0' or '0'
    c = 'Q';
}
std::cerr << "\n";
for (char & c : str) {
    std::cerr << c;
}
std::cerr << "\n";

Aqui está o que este programa produz para mim:

Entering loop.
Entering loop.

vector::_M_emplace_ba
QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ

Essa foi minha primeira declaração impressa duas vezes, vários caracteres não impressos, seguidos por uma nova linha, seguida por algo na memória interna, que acabei de sobrescrever (e depois imprimi, mostrando que foi sobrescrito). Pior de tudo, mesmo compilar isso com avisos gcc completos e detalhados não me deu nenhuma indicação de que algo estava errado, e rodar o programa através do valgrind não reclamou de nenhum padrão de acesso à memória impróprio. Em outras palavras, é completamente indetectável por ferramentas modernas.

Você pode ter esse mesmo problema com o muito mais simples std::string("0", 100);, mas o exemplo acima é um pouco mais complicado e, portanto, mais difícil de ver o que está errado.

Felizmente, C ++ 11 nos dá uma boa solução para o problema usando a sintaxe da lista de inicializadores. Isso evita que você especifique o número de caracteres (o que, como mostrei acima, você pode fazer incorretamente) e evita combinar números de escape. std::string str({'a', '\0', 'b'})é seguro para qualquer conteúdo de string, ao contrário das versões que têm uma matriz de chare um tamanho.


2
Como parte da preparação para esta postagem, enviei um relatório de bug ao gcc na esperança de que eles adicionem um aviso para tornar isso um pouco mais seguro: gcc.gnu.org/bugzilla/show_bug.cgi?id=54924
David Stone

4

Em C ++ 14 agora você pode usar literais

using namespace std::literals::string_literals;
std::string s = "a\0b"s;
std::cout << s.size(); // 3

1
e a 2ª linha também pode ser escrita, de forma mais adequada, imho, comoauto s{"a\0b"s};
sublinhado_d

Boa resposta, obrigado.
Joma

1

Melhor usar std :: vector <char> se esta questão não for apenas para fins educacionais.


1

A resposta do anônimo é excelente, mas também há uma solução não macro em C ++ 98:

template <size_t N>
std::string RawString(const char (&ch)[N])
{
  return std::string(ch, N-1);  // Again, exclude trailing `null`
}

Com esta função, RawString(/* literal */)produzirá a mesma string que S(/* literal */):

std::string my_string_t(RawString("a\0b"));
std::string my_string_m(S("a\0b"));
std::cout << "Using template: " << my_string_t << std::endl;
std::cout << "Using macro: " << my_string_m << std::endl;

Além disso, há um problema com a macro: a expressão não é realmente std::stringcomo escrita e, portanto, não pode ser usada, por exemplo, para inicialização de atribuição simples:

std::string s = S("a\0b"); // ERROR!

... então pode ser preferível usar:

#define std::string(s, sizeof s - 1)

Obviamente, você só deve usar uma ou outra solução em seu projeto e chamá-la do que achar apropriado.


-5

Eu sei que faz muito tempo que essa pergunta foi feita. Mas, para quem está tendo um problema semelhante, pode se interessar pelo código a seguir.

CComBSTR(20,"mystring1\0mystring2\0")

Esta resposta é muito específica para plataformas Microsoft e não aborda a questão original (que era sobre std :: string).
Junho Rhodes

-8

Quase todas as implementações de std :: strings têm terminação nula, então você provavelmente não deveria fazer isso. Observe que "a \ 0b" tem na verdade quatro caracteres por causa do terminador nulo automático (a, nulo, b, nulo). Se você realmente deseja fazer isso e quebrar o contrato de std :: string, você pode fazer:

std::string s("aab");
s.at(1) = '\0';

mas se você fizer isso, todos os seus amigos vão rir de você, você nunca encontrará a verdadeira felicidade.


1
std :: string NÃO precisa ter terminação NULL.
Martin York,

2
Não é obrigatório, mas em quase todas as implementações é, provavelmente devido à necessidade do acessador c_str () fornecer o equivalente terminado em nulo.
Jurney,

2
Para maior eficiência, um caractere nulo pode ser mantido na parte de trás do buffer de dados. Mas nenhuma das operações (ou seja, métodos) em uma string usa esse conhecimento ou é afetada por uma string contendo um caractere NULL. O caractere NULL será manipulado exatamente da mesma maneira que qualquer outro caractere.
Martin York,

É por isso que é tão engraçado que string seja std :: - seu comportamento não é definido em NENHUMA plataforma.

Gostaria que o usuário 595447 ainda estivesse aqui para que eu pudesse perguntar sobre o que eles achavam que estavam falando.
sublinhado_d
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.