Dado dizer ...
std::string x = "hello";
Obtendo um `char *` ou `const char *` de uma `string`
Como obter um ponteiro de caractere válido enquanto x
permanece no escopo e não é mais modificado
C ++ 11 simplifica as coisas; todos os seguintes dão acesso ao mesmo buffer interno de cadeias de caracteres:
const char* p_c_str = x.c_str();
const char* p_data = x.data();
char* p_writable_data = x.data(); // for non-const x from C++17
const char* p_x0 = &x[0];
char* p_x0_rw = &x[0]; // compiles iff x is not const...
Todos os ponteiros acima conterão o mesmo valor - o endereço do primeiro caractere no buffer. Mesmo uma string vazia possui um "primeiro caractere no buffer", porque o C ++ 11 garante sempre manter um caractere terminador NUL / 0 extra após o conteúdo da string explicitamente designado (por exemplo, std::string("this\0that", 9)
manterá o buffer "this\0that\0"
).
Dado qualquer um dos indicadores acima:
char c = p[n]; // valid for n <= x.size()
// i.e. you can safely read the NUL at p[x.size()]
Somente para o não const
ponteiro p_writable_data
e de &x[0]
:
p_writable_data[n] = c;
p_x0_rw[n] = c; // valid for n <= x.size() - 1
// i.e. don't overwrite the implementation maintained NUL
Escrever um NUL em outro lugar da string não altera os string
's size()
; string
é permitido conter qualquer número de NULs - eles não recebem tratamento especial por std::string
(o mesmo em C ++ 03).
No C ++ 03 , as coisas eram consideravelmente mais complicadas (principais diferenças destacadas ):
x.data()
- retorna
const char*
ao buffer interno da string que não era exigido pelo Padrão para concluir com um NUL (ou seja, pode ser ['h', 'e', 'l', 'l', 'o']
seguido por valores não inicializados ou de lixo, com acessos acidentais a ele com comportamento indefinido ).
x.size()
caracteres são seguros de ler, ou seja, x[0]
atravésx[x.size() - 1]
- para cadeias vazias, você garante um ponteiro não NULL ao qual 0 pode ser adicionado com segurança (viva!), mas você não deve desreferenciar esse ponteiro.
&x[0]
- para cadeias vazias, isso tem um comportamento indefinido (21.3.4)
- por exemplo, dado que
f(const char* p, size_t n) { if (n == 0) return; ...whatever... }
você não deve ligar f(&x[0], x.size());
quando x.empty()
- apenas use f(x.data(), ...)
.
- caso contrário, conforme por
x.data()
mas:
- para não-
const
x
isso produz um não- const
char*
ponteiro; você pode substituir o conteúdo da string
x.c_str()
- retorna
const char*
para uma representação ASCIIZ (terminada em NUL) do valor (ou seja, ['h', 'e', 'l', 'l', 'o', '\ 0']).
- embora poucas implementações tenham optado por fazê-lo, o C ++ 03 Standard foi redigido para permitir à implementação da cadeia a liberdade de criar um buffer distinto terminado por NUL em tempo real , a partir do buffer potencialmente não terminado por NUL "exposto" por
x.data()
e&x[0]
x.size()
É seguro ler + 1 caracteres.
- seguro garantido, mesmo para cadeias vazias (['\ 0']).
Consequências do acesso a índices legais externos
Qualquer que seja a maneira como você obtém um ponteiro, não deve acessar a memória mais além do ponteiro do que os caracteres garantidos presentes nas descrições acima. As tentativas de fazer isso têm um comportamento indefinido , com uma chance muito real de travamentos de aplicativos e resultados de lixo, mesmo para leituras e dados adicionais adicionais, empilham corrupção e / ou vulnerabilidades de segurança para gravações.
Quando esses ponteiros são invalidados?
Se você chamar alguma string
função de membro que modifique string
ou reserve mais capacidade, quaisquer valores de ponteiro retornados anteriormente por qualquer um dos métodos acima serão invalidados . Você pode usar esses métodos novamente para obter outro ponteiro. (As regras são as mesmas que para os iteradores em string
s).
Consulte também Como obter um ponteiro de caractere válido mesmo depois que x
sai do escopo ou é modificado mais abaixo ....
Então, qual é o melhor para usar?
No C ++ 11, use .c_str()
para dados ASCIIZ e .data()
para dados "binários" (explicados mais abaixo).
No C ++ 03, use a .c_str()
menos que .data()
seja adequado e prefira .data()
o &x[0]
contrário, pois é seguro para cadeias de caracteres vazias ....
... tente entender o programa o suficiente para usar data()
quando apropriado, ou você provavelmente cometerá outros erros ...
O caractere ASCII NUL '\ 0' garantido por .c_str()
é usado por muitas funções como um valor sentinela que indica o fim dos dados relevantes e de acesso seguro. Isso se aplica às funções C ++ - somente funções como say fstream::fstream(const char* filename, ...)
e funções compartilhadas com C como strchr()
e printf()
.
Dadas as .c_str()
garantias de C ++ 03 sobre o buffer retornado, são um superconjunto de .data()
's, você sempre pode usar com segurança .c_str()
, mas às vezes as pessoas não o fazem porque:
- usando
.data()
comunica-se com outros programadores lendo o código fonte de que os dados não são ASCIIZ (em vez disso, você está usando a string para armazenar um bloco de dados (que às vezes nem é realmente textual)) ou que está passando esses dados para outra função que a trata como um bloco de dados "binários". Isso pode ser um insight crucial para garantir que as alterações no código de outros programadores continuem a manipular os dados corretamente.
- Somente C ++ 03: há uma pequena chance de que sua
string
implementação precise fazer uma alocação de memória extra e / ou cópia de dados para preparar o buffer terminado por NUL
Como uma dica adicional, se os parâmetros de uma função exigirem o ( const
), char*
mas não insistirem em obter x.size()
, a função provavelmente precisará de uma entrada ASCIIZ, portanto, .c_str()
é uma boa opção (a função precisa saber onde o texto termina de alguma forma, por isso, se não estiver) um parâmetro separado, ele pode ser apenas uma convenção, como um prefixo de comprimento ou sentinela ou algum comprimento esperado fixo).
Como obter um ponteiro de caractere válido mesmo depois que x
sai do escopo ou é modificado ainda mais
Você precisará copiar o conteúdo da página string
x
para uma nova área de memória externa x
. Esse buffer externo pode estar em muitos locais, como outra string
variável ou matriz de caracteres, pode ou não ter uma vida útil diferente da x
devida a estar em um escopo diferente (por exemplo, namespace, global, estático, heap, memória compartilhada, arquivo mapeado pela memória) .
Para copiar o texto de std::string x
para uma matriz de caracteres independente:
// USING ANOTHER STRING - AUTO MEMORY MANAGEMENT, EXCEPTION SAFE
std::string old_x = x;
// - old_x will not be affected by subsequent modifications to x...
// - you can use `&old_x[0]` to get a writable char* to old_x's textual content
// - you can use resize() to reduce/expand the string
// - resizing isn't possible from within a function passed only the char* address
std::string old_x = x.c_str(); // old_x will terminate early if x embeds NUL
// Copies ASCIIZ data but could be less efficient as it needs to scan memory to
// find the NUL terminator indicating string length before allocating that amount
// of memory to copy into, or more efficient if it ends up allocating/copying a
// lot less content.
// Example, x == "ab\0cd" -> old_x == "ab".
// USING A VECTOR OF CHAR - AUTO, EXCEPTION SAFE, HINTS AT BINARY CONTENT, GUARANTEED CONTIGUOUS EVEN IN C++03
std::vector<char> old_x(x.data(), x.data() + x.size()); // without the NUL
std::vector<char> old_x(x.c_str(), x.c_str() + x.size() + 1); // with the NUL
// USING STACK WHERE MAXIMUM SIZE OF x IS KNOWN TO BE COMPILE-TIME CONSTANT "N"
// (a bit dangerous, as "known" things are sometimes wrong and often become wrong)
char y[N + 1];
strcpy(y, x.c_str());
// USING STACK WHERE UNEXPECTEDLY LONG x IS TRUNCATED (e.g. Hello\0->Hel\0)
char y[N + 1];
strncpy(y, x.c_str(), N); // copy at most N, zero-padding if shorter
y[N] = '\0'; // ensure NUL terminated
// USING THE STACK TO HANDLE x OF UNKNOWN (BUT SANE) LENGTH
char* y = alloca(x.size() + 1);
strcpy(y, x.c_str());
// USING THE STACK TO HANDLE x OF UNKNOWN LENGTH (NON-STANDARD GCC EXTENSION)
char y[x.size() + 1];
strcpy(y, x.c_str());
// USING new/delete HEAP MEMORY, MANUAL DEALLOC, NO INHERENT EXCEPTION SAFETY
char* y = new char[x.size() + 1];
strcpy(y, x.c_str());
// or as a one-liner: char* y = strcpy(new char[x.size() + 1], x.c_str());
// use y...
delete[] y; // make sure no break, return, throw or branching bypasses this
// USING new/delete HEAP MEMORY, SMART POINTER DEALLOCATION, EXCEPTION SAFE
// see boost shared_array usage in Johannes Schaub's answer
// USING malloc/free HEAP MEMORY, MANUAL DEALLOC, NO INHERENT EXCEPTION SAFETY
char* y = strdup(x.c_str());
// use y...
free(y);
Outras razões para querer um char*
ou const char*
gerado a partir de umstring
Então, acima, você viu como obter um ( const
) char*
e como fazer uma cópia do texto independente do original string
, mas o que você pode fazer com ele? Um punhado aleatório de exemplos ...
- conceda acesso ao código "C" ao
string
texto do C ++ , como emprintf("x is '%s'", x.c_str());
- copiar
x
o texto para um buffer especificado pelo responsável pela chamada da função (por exemplo strncpy(callers_buffer, callers_buffer_size, x.c_str())
) ou memória volátil usada para E / S do dispositivo (por exemplo for (const char* p = x.c_str(); *p; ++p) *p_device = *p;
)
- anexar
x
o texto a uma matriz de caracteres que já contenha algum texto ASCIIZ (por exemplo strcat(other_buffer, x.c_str())
) - tome cuidado para não exceder o buffer (em muitas situações, você pode precisar usar strncat
)
- retornar uma
const char*
ou char*
de uma função (talvez por razões históricas - o cliente está usando sua API existente - ou para compatibilidade com C, você não deseja retornar a std::string
, mas deseja copiar string
os dados de algum lugar para o chamador)
- tenha cuidado para não retornar um ponteiro que possa ser desreferenciado pelo chamador depois que uma
string
variável local para a qual esse ponteiro apontado deixou o escopo
- alguns projetos com objetos compartilhados compilados / vinculados para diferentes
std::string
implementações (por exemplo, STLport e nativo do compilador) podem passar dados como ASCIIZ para evitar conflitos