Método c_str()

256 views
Skip to first unread message

Márcio Gil

unread,
May 22, 2008, 9:15:31 AM5/22/08
to C-cpp-brasil
Bom dia,

Estudando a STL me bateu uma dúvida. Qual a duração do valor retornado pelo
método c_str() da std::string? Porque utilizei o C++ Builder por muitos anos
e o método c_str() da AnsiString é problemático no sentido de que eu não
posso fazer isso:

void f( AnsiString &str )
{
const char *tmp;
tmp = str.c_str();
... // Utilizar o tmp pode fazer o programa travar!
}

mas tenho que fazer assim:

void f( AnsiString &str )
{
h( str.c_str() );
}

void h( const char *tmp )
{
...
}

ou assim:

void f( AnsiString &str )
{
char *tmp = new char[ str.Length() + 1 ];
strcpy( tmp, str.c_str() );
...
delete[] tmp;
}

Tenho que ter algum cuidado ao utilizar o método c_str() da std::string? Ou
posso perfeitamente fazer isso:

void f( std::string &str )
{
const char *tmp;
tmp = str.c_str();
... // Posso utilizar o tmp sem medo?
}


Obrigado pela atenção,

Márcio Gil.

Pedro Lamarão

unread,
May 22, 2008, 12:58:01 PM5/22/08
to ccppbrasil
On 22 maio, 10:15, Márcio Gil <marciom...@bol.com.br> wrote:

> void f( AnsiString &str )
> {
> const char *tmp;
> tmp = str.c_str();
> ... // Utilizar o tmp pode fazer o programa travar!
> }

A implementação tem a liberdade de invalidar este ponteiro __se__ você
chamar um método não-const em str.
Caso contrário, é seguro acessar esse endereço.

--
P.

Cesar Mello

unread,
May 22, 2008, 1:03:57 PM5/22/08
to ccppb...@googlegroups.com
Márcio,

Enquanto o objeto string não for destruído E seu conteúdo não for
modificado, você pode usar o buffer sem problemas.

Da mesma forma que o std::vector, o std:string mantém um array alocado
dinamicamente para os caracteres. Quando a string crescer além do
tamanho do array, ele será realocado, portanto o ponteiro antigo vai
estar apontando para o buffer que já foi desalocado.

Utilize o c_str() para passar o buffer apenas para funções de APIs
antigas que não modifiquem a string, /e nunca guarde esse ponteiro/.
Para todos os outros casos passe uma referência ou o próprio objeto
std::string.

[]
Mello

Felipe Magno de Almeida

unread,
May 22, 2008, 7:42:29 PM5/22/08
to ccppb...@googlegroups.com
2008/5/22 Cesar Mello <cme...@gmail.com>:

> Márcio,
>
> Enquanto o objeto string não for destruído E seu conteúdo não for
> modificado, você pode usar o buffer sem problemas.

Não exatamente. Veja abaixo.

> Da mesma forma que o std::vector, o std:string mantém um array alocado
> dinamicamente para os caracteres. Quando a string crescer além do
> tamanho do array, ele será realocado, portanto o ponteiro antigo vai
> estar apontando para o buffer que já foi desalocado.

Este conselho não é completamente seguro ou completo.
Em std::vector<T>, você pode fazer operações que modifiquem
std::vector<T> sem problemas enquanto usa um ponteiro
para o primeiro elemento do vetor, desde que nenhuma operação
ultrapasse o capacity do std::vector<T>. Com c_str() do std::string
isto não é garantido, qualquer acesso a funções-membro não-const
podem invalidar o ponteiro retornado por c_str(). O conselho do Pedro
por exemplo é exatamente correto. Mesmo funções não-const como
operator[] podem invalidar c_str() mesmo que não haja modificação
do caractere retornado.
E só para ser um pouco mais pedante, Márcio, tome cuidado
para não usar o ponteiro retornado por c_str() depois que o tempo de
vida do objeto tenha terminado. Isto pode acontecer facilmente
ao se guardar o ponteiro retornado por c_str(). O que nos leva ao
segundo conselho:

> Utilize o c_str() para passar o buffer apenas para funções de APIs
> antigas que não modifiquem a string, /e nunca guarde esse ponteiro/.
> Para todos os outros casos passe uma referência ou o próprio objeto
> std::string.

Este conselho é muito bom. A única necessidade que se pode haver
de usar c_str() ao invés de std::string diretamente é de utilizar essa
string em APIs fixas que já usem char*/const char*. Desde que a
mesma não modifique a c-string retornada por c_str().

> []
> Mello


--
Felipe Magno de Almeida

Cesar Mello

unread,
May 22, 2008, 10:50:24 PM5/22/08
to ccppb...@googlegroups.com

> Este conselho não é completamente seguro ou completo.
> Em std::vector<T>, você pode fazer operações que modifiquem
> std::vector<T> sem problemas enquanto usa um ponteiro
> para o primeiro elemento do vetor, desde que nenhuma operação
> ultrapasse o capacity do std::vector<T>. Com c_str() do std::string
> isto não é garantido, qualquer acesso a funções-membro não-const
> podem invalidar o ponteiro retornado por c_str(). O conselho do Pedro
> por exemplo é exatamente correto. Mesmo funções não-const como
> operator[] podem invalidar c_str() mesmo que não haja modificação
> do caractere retornado.
>
Consultei o meu TC++PL e ele confirma exatamente isso que o Lamarão e o
Felipe afirmaram.

"The user also cannot rely on its value after a subsequent call on a
non-const function on the string".

Peço perdão pelo meu equívoco.

[]
Mello

Márcio Gil

unread,
May 23, 2008, 5:04:38 PM5/23/08
to ccppb...@googlegroups.com
Pedro, Cesar e Felipe,

Obrigado pelas dicas e pela explicação. Meu objetivo mesmo era só efetuar
leituras no ponteiro retornado pelo método do c_str()... Mas vou explicar
melhor:

A uns quatro anos eu desenvolvi algumas rotinas de manipulação de strings
(SQL) com a AnsiString do C++ Builder e elas eram extremamente lentas. Então
eu resolvi modifica-las para trabalhar com char*, porém precisei manter
compatilibilidade com o código já pronto, de modo que eu simplesmente pegava
o ponteiro com o c_str() e efetuava o processamento. Alguns dias depois eu
tive problemas com travamentos nos programas. Depois de muita aporrinhação
para descobrir a origem do problema, eu encontrei no manual do BCB a
informação que o ponteiro retornado por c_str() era temporário, ou seja,
perdia a validade tão logo o programa pulava para a linha de código
seguinte.

Seria até um teste interessante eu reimplementar estas rotinas com a
std::string e comparar o tempo de processamento com a versão que utiliza
char*. Utilizando o std::string::const_iterator imagino que o desempenho
seja satisfatório.

Márcio Gil.

> -----Mensagem original-----
> De: ccppb...@googlegroups.com Em nome de Pedro Lamarão

Gianni

unread,
May 25, 2008, 10:26:34 AM5/25/08
to ccppb...@googlegroups.com
> A uns quatro anos eu desenvolvi algumas rotinas de manipulação de strings
> (SQL) com a AnsiString do C++ Builder e elas eram extremamente lentas.
> Então eu resolvi modifica-las para trabalhar com char*, porém precisei
> manter compatilibilidade com o código já pronto, de modo que eu
> simplesmente pegava o ponteiro com o c_str() e efetuava o processamento.
> Alguns dias depois eu tive problemas com travamentos nos programas. Depois
> de muita aporrinhação para descobrir a origem do problema, eu encontrei no
> manual do BCB a informação que o ponteiro retornado por c_str() era
> temporário, ou seja, perdia a validade tão logo o programa pulava para a
> linha de código seguinte.
Na minha experiência com Delphi, tive problemas parecidos. Se o compilador do
Builder se comportar parecido com o do Delphi, pode ser que o que está
acontecendo é que ele vê que na função f vc só usa o str na 2a linha, e
depois disso, nunca mais usa o _str_ por isso o compilador acha seguro já
destruir o já que ninguém mais tem referência à ele. Nunca usei o Builder,
então não tenho certeza disso...

faça um teste assim:

int f( AnsiString &str )


{
const char *tmp;
tmp = str.c_str();
...     // Utilizar o tmp pode fazer o programa travar!

return str.length(); //<-- não deixe o compilador destruir o teu string
}

signature.asc

Rafael Giusti

unread,
May 27, 2008, 8:21:19 AM5/27/08
to ccppb...@googlegroups.com
2008/5/25 Gianni <nasus....@gmail.com>:
(...)


faça um teste assim:

int f( AnsiString &str )
{
const char *tmp;
tmp = str.c_str();
...     // Utilizar o tmp pode fazer o programa travar!

return str.length(); //<-- não deixe o compilador destruir o teu string
}

Isso não pode dar em nada. Afinal, str é uma referência. Mesmo que o compilador destrua objetos antes do término da função, ele não iria chamar o destrutor de uma referência.

[]'s
rg

--
Rafael Giusti
Laboratório de Inteligência Computacional - LABIC
Universidade de São Paulo - USP

Márcio Gil

unread,
May 27, 2008, 9:28:27 PM5/27/08
to ccppb...@googlegroups.com
---- Mensagem original ----
> De: ccppb...@googlegroups.com Em nome de Rafael Giusti

>
> 2008/5/25 Gianni <nasus....@gmail.com>:
> >
> > (...)
> >
> > faça um teste assim:
> >
> > int f( AnsiString &str )
> > {
> > const char *tmp;
> > tmp = str.c_str();
> > ... // Utilizar o tmp pode fazer o programa travar!
> >
> >
> > return str.length(); //<-- não deixe o compilador destruir o teu
> > // string

> > }
> >
>
>
> Isso não pode dar em nada. Afinal, str é uma referência. Mesmo que
> o compilador destrua objetos antes do término da função, ele não
> iria chamar o destrutor de uma referência.
>

Talvez você tenha razão quanto a isto, acho que a função
problemática era declarada assim:

int f( AnsiString str )
{
...
}

Mas a documentação do BCB não fala nada de destrutores:

<----
AnsiString::c_str() returns a non const temporary pointer to the
internal string buffer in the AnsiString object. The pointer is
invalid once the statement in which it is used has finished
executing. That is, don't do something like this:

char* cp = Edit1->Text.c_str();
char* cp2 = strtok( cp, " \t\n" ); // cp may no longer be valid

If you need a persistent pointer, you MUST copy the string into its
own buffer:

char* cp = new char[ Edit1->Text.Length() + 1 ];
strcpy( cp, Edit1->Text.c_str() );
---->


Certamente não tem nada a ver com o método c_str() da std::string.


Márcio Gil

unread,
May 27, 2008, 9:28:27 PM5/27/08
to ccppb...@googlegroups.com
> -----Mensagem original-----
> De: ccppb...@googlegroups.com Em nome de Gianni

>
> Na minha experiência com Delphi, tive problemas parecidos. Se o
> compilador do Builder se comportar parecido com o do Delphi,

Na verdade o C++ Builder é o próprio Delphi utilizando sintaxe C++ ao
invés de Pascal :-)

Se não me engano, a maioria das libs do BCB foram escritas em Pascal
e os arquivos header gerados a partir dos arquivos .pas (é essa a
informação presente nos arquivos .hpp da VCL: DO NOT EDIT: machine
generated header)

> pode ser que o que está acontecendo é que ele vê que na função f
> vc só usa o str na 2a linha, e depois disso, nunca mais usa o
> _str_ por isso o compilador acha seguro já destruir o já que
> ninguém mais tem referência à ele. Nunca usei o Builder, então
> não tenho certeza disso...
>
> faça um teste assim:
>
> int f( AnsiString &str )
> {
> const char *tmp;
> tmp = str.c_str();
> ...     // Utilizar o tmp pode fazer o programa travar!
>
> return str.length(); //<-- não deixe o compilador destruir o

> // teu string
> }
>

Muito interessante, faz sentido. Porém estou aos poucos abandonando
o BCB para escrever programas o mais fiel possível ao padrão
ISO-C++. Mas obrigado pela dica.

Márcio Gil.

Reply all
Reply to author
Forward
0 new messages