Home
C/C++
Crashproof
 
Crashproof


Use const em todas as oportunidades

const é a sua arma anti-crash mais poderosa. Use-a em todas as oportunidades. Um benefício adicional do uso de const é tornar o seu código mais legível e auto documentado. A propósito, veja esse exemplo:
const char *add2strings(const char *sz1, const char *sz2);
Esse exemplo é como uma declaração de garantia de que, não importando que tipo de coisa aconteça com a função, nada poderá causar dano aos strings sz1 e sz2. Se ocorrer corrupção de memória, somente as variáveis dentro do escopo da função serão afetadas. Isso, é claro, reduz drasticamente os efeitos secundários dos erros de programação. Mas é claro que todo esses benefícios se perdem se o programa usa variáveis globais...

Mais ainda, a declaração do pointer de retorno como uma const significa que o programador da aplicação não consegue alcançá-lo de dentro da função para corrompê-lo. Por exemplo, se o valor de retorno é um array estático de 40 caracteres, mas a especificação do retorno da função não é estática, o programador da aplicação poderia fazer o seguinte:

char *pchName = add2strings("Philbinoff", "James");
strcat(pchName, " is the name of the first author of the three books");
cout << "I just corrupted an internal variable of add2strings. ";
cout << "Will I see this message?\n";
cout << "Will it crash later in the program? Who knows!\n";
Felizmente, porque add2strings() retorna um pointer const, a construção acima geraria um erro de compilação em

char *pchName = add2strings("Philbinoff", "James");

Mesmo que você declare pchName como const char *, no momento em que você modificar o seu conteúdo com strcat() você receberá um erro de compilação. Portanto a especificação const ajuda o programador a manter restritos os efeitos de seus erros, o que reduz em muito a confusão gerada pelos efeitos secundários dos erros de programação.
Cuidado com a manipulação de strings

A experiência mostra que a primeira causa de corrupção de memória – retenção, GPF e outros problemas incômodos e difíceis de localizar – é a manipulação de strings. Nos velhos e difíceis tempos da linguagem C isso era inevitável, agora, com C++, isso não precisa mais ser um problema.

Se você puder, construa uma classe string.

Você já usou strings em outras linguagens? Não é bem mais fácil? A punição por um erro não é um GPF em outro ponto do programa, "que ocorre em algumas instalações e não em outras". Em outras linguagens o seu código para a manipulação de string funciona ou não funciona! E você sabe imediatamente se funciona bem ou não. Você pode obter esse mesmo nível de confiança em manipulação de strings em C++ se usar um classe string.

Há várias implementações possíveis de classe string. Stroustrup descreve uma implementação simples no capítulo 7, seção 11, de seu livro The C++ Programming Language, segunda edição. Sua empresa pode fazer sua própria implementação. Seja qual for a implementação que você usar, sua classe string deve ser capaz de mover dados para (input) e de (output) strings, concatenar, pesquisar e substituir, etc. sem que o programador de aplicação tenha que lidar com a aritmética dos pointers. Tal implementação deveria usar o sinal de soma (+) para a operação de concatenação.

Se sua empresa não implementou uma classe string, você ainda pode ter muitos benefícios usando os strings C++ stream. Um string stream é um stream que escreve em um string ao invés de escrever em um arquivo. Você pode declará-lo sem definir um tamanho máximo, e manejá-lo adicionando strings. Por exemplo

//***** Create nametag string ******
ostrstream ost; 
ost << "My name is " << ychFname;
ost.put(0);                  //null terminate the string 
makenametag(ost.str());
//***** 
//Also create a nametag with the last name after first 
//*****
ost.seekp(ost.tellp()-1);    //get rid of the null termination
ost << " " << ychLname;
ost.put(0);                  //null terminate the string
makenametag(ost.str());
Mesmo que você não use uma classe string, há vários modos de se manejar, inteligentemente, os strings terminados em zero. O exemplo seguinte não é uma das maneiras inteligentes.
char *pchPerson = "Magdelena Alexandra de la Romero";
char ychName[20];
strcpy(ychName, pchPerson); //CRUNCH!
cout << ychName << '\n';    //what this will output is anybody's guess.
Agora veja um manejo mais inteligente de string terminado em zero, usando strcpy.
char *pchPerson = "Magdalena Alexandra de la Romero";
char ychName[20];
strncpy(ychName, pchPerson, sizeof(ychName)-1);
ychName[sizeof(ychName)-1] = '\0';
cout << ychName << '\n';    //Magdelena Alexandra
Aqui uma outra solução, que também não corrompe memória.
char *pchPerson = "Magdalena Alexandra de la Romero";
char pchName = new char[strlen(pchPerson) + 1];
strcpy(pchName, pchPerson);
cout << ychName << '\n'; //Magdelena Alexandra de la Romero
//*** 
//Of course, sooner or later pchName must be deleted 
//***
if(pchName)
  {
  delete(pchName)
  pchName = 0;
  }
Proteja seu stack

Variáveis locais escapam do controle do stack.

Talvez a principal restrição que se possa fazer ao C++ é o fato de que as variáveis locais escapem do controle do stack, ou seja, o manejo de variáveis locais pode exceder a capacidade do stack. Se você tiver uma ocorrência de variável local reservando uma área de memória muito grande, ou um grande número de ocorrências de variáveis pequenas declaradas como locais, você pode exceder a capacidade do stack e provocar um erro que provavelmente será percebido em algum outro ponto de seu programa.

//***** This might cause a big problem *****
class bufferhandler
 {
 void zero(){*buf='\0';};
 void put(const char *pch){strncpy(buf, pch, sizeof(buf)-1);};
 const char *get(){return(buf);};
 private:
 char buf[2000];
 };
void myprocess(void)
 {
 bufferhandler bh1;           //2000+ bytes gone from the stack
 bufferhandler bh2;           //Another 2000+ gone. 
                              //Will there be enough?
 bh1.put("Steve was here");
 bh2.put("Sylvia was here");
 /* . . . */
 }  
Uma maneira de contornar o problema, embora não muito elegante, é usar pointers.
void myprocess(void)
 {
 bufferhandler *pbh1 = new bufferhandler; 
                       //4 bytes gone for the pointer
 bufferhandler *pbh2 = new bufferhandler; 
                       //Another 4 gone. Stack is cool.
 pbh1->put("Steve was here");
 pbh2->put("Fred was here");
 /* . . . */
 //***** be sure to delete the pointers *****
 if(pbh1)
  {
  delete(pbh1);
  pbh1=0;
  }
 if(pbh2)
  {
  delete(pbh2);
  pbh2=0;
  }
 }
Ocasionalmente alguém comete esse erro. Se esquece que a variável local está no stack e passa um pointer para a variável local de volta, como retorno da função. Se o código que usa o pointer devolvido não escrever na variável referenciada pelo pointer , nada de grave acontecerá. Contudo, algumas vezes, dependendo do mapa de memória, o valor retornado se altera como que por mágica, como se algo no stack sobrescrevesse a variável agora fora de escopo.
//***** BONEHEAD MISTAKE *****
char *addstrings(const char *sz1, const char *sz2)
 {
 char ychDst[100];    //life would be easier 
                      //if I had made this static
 strncpy(ychDst, sz1, sizeof(ychDst) - 1);
 strncpy(ychDst + n1, sz2, sizeof(ychDst) - strlen(sz1) - 1);
 ychDst[sizeof(ychDst) - 1] = '\0';
 return(ychDst];      //BONEHEAD
 }
Esqueça o C

Esqueça que um dia você aprendeu printf(). Use cout <<. É claro, scanf() foi "companheira do desastre" naqueles longos e passados dias da linguagem C. Use cin >>, ou construa suas próprias rotinas de entrada ou classes. E sobre aquela maravilhosa sprintf()? Em lugar disso:
#include <iostream.h>
#include <stdio.h>
/* . . . */
char ychString[100];
sprintf(ychString, "%s, %s %c", ychLname, ychFname, *ychMname);
cout << ychString << '\n';
experimente isso:
#include <iostream.h>
#include <strstream.h>
/* . . . */
ostrstream ost;
ost << ychLname << ", " << ychFname << " " << *ychMname;
ost.put(0); //null terminate the string cout
cout << ost.str() << '\n';

| Home | Bookmarks | Universidades | Para Saber mais | Universidades | WEB Directory | Mapa do site |