paulo1205
(usa Ubuntu)
Enviado em 17/04/2017 - 14:39h
Caro Bruno,
Acho que sua resposta não tem muito a ver com as perguntas trazidas originalmente, mas não vou entrar nesse assunto.
Quero apenas apontar um problema no breve trecho de código que você mostrou (muito comum, por sinal -- provavelmente você até foi ensinado a fazer desse modo errôneo). Na verdade, um problema mais sério e outros dois relacionados mais a estilo, mas que podem vir a se tornar problemas maiores numa eventual evolução do código.
Lista *ls = (Lista*) malloc (sizeof(Lista)); // ls terá somente uma posição no ponteiro somente o ls[0];
Crítica de estilo: em vez de fazer do modo como você fez, prefira o seguinte modo.
Lista *ls=malloc(sizeof *ls);
Porquês:
1) É o que programadores experientes em C esperariam encontrar. É geralmente bom você trabalhar alinhado com os jargões e construções que a comunidade da linguagem utiliza.
2) Você faz com que o compilador decida o tamanho do dado apontado de acordo com o tipo do próprio ponteiro que vai receber o apontamento, não com um nome de tipo arbirtário. Não é o seu caso, pois a declaração e a definição da valor estão na mesma linha, mas é muito comum num caso genérico que declaração e atribuição fiquem em linhas distintas, e pode acontecer de você fazer a manutenção numa linha e esquecer de a fazer em outra (por exemplo, mudando tipo de dado associado ao ponteiro). Ao diminuir a quantidade de interdependências, você reduz o esforço de manutenção.
3)
malloc () devolve um dado do tipo
void * , que, em C (mas não em C++), é automaticamente conversível para qualquer outro tipo de ponteiro, de modo que a conversão explícita é redundante. Sendo redundante, aumenta desnecessariamente o esforço de manutenção, facilitando o aparecimento de bugs. Pior ainda: a conversão explícita de ponteiros pode ativamente
induzir ao aparecimento de bugs, pois ela desabilita eventuais testes de compatibilidade que o compilador poderia realizar (que não se aplicam a
void * , logo não ao resultado de
malloc (), mas se aplicam a outros tipos de ponteiros e a combinações perigosas entre ponteiros e inteiros).
Uma razão para que se faça a conversão explícita é permitir o uso de compiladores anteriores à padronização do C (que ocorreu em 1989), uma vez que o suporte a
void * , sua semântica e seu uso como parte da biblioteca padrão de funções só ficaram bem definidos após a publicação do padrão ANSI C, posteriormente ratificado como ISO C, em 1990(*). Como, porém, a instituição de
void * caminha para completar sua terceira década, é muito improvável que alguém realmente precise trabalhar compiladores obsoletos hoje em dia, logo a conversão explícita não deveria ser feita.
Outra possível justificativa é permitir que o código seja compilado sem erros também em C++. Só que isso vai provocar narizes torcidos tanto na comunidade de programadores em C quanto na dos em C++. Os primeiros vão alegar as mesma coisas alegadas acima, e os mais puristas costumam ter ojeriza a C++, de modo que desprezam a “poluição” provocada pela compatibilidade com a ferramenta que eles abominam. No lado do C++, o ódio ao outro é bem menor, mas geralmente se repudia tanto a conversão de tipos ao estilo do C, por ser insegura (e realmente o é), quanto a gestão de memória baseada em funções da biblioteca. Geralmente se prefere em C++ o uso dos operadores
new , para alocação de elementos simples, e
new [] , para alocação de arrays, e seus respectivos correspondentes de desalocação,
delete e
delete[] , ou então o uso de um objeto de uma dos
templates de classes de
containers , como
std::vector ,
std::list ou
std::deque .
//para 2 posições
ls = (Lista*) realloc (ls, sizeof(Lista) * 2); // ls terá 2 posições para o ponteiro, ls[0], ls[1].
Aqui o erro principal, que me levou a escrever esta postagem.
Lembre-se que o C sempre passa como parâmetros para as funções meras
cópias dos valores daquilo que você colocar na lista de argumentos na hora em que invocar a função. Desse modo, quando você diz “
realloc(ls, N) ”, você está pegando uma cópia do valor de
ls e uma cópia do valor de
N , e entregando essas cópias para a função invocada.
A função
realloc () foi projetada de modo a
tentar fazer a realocação. Quer a realocação tenha sucesso, quer não, a função garante que os dados originais apontados pelo ponteiro para a área que tem de ser realocada serão preservados. Existem três possibilidades de conclusão da tentativa de realocação, a saber:
1) A função consegue alterar diretamente o tamanho da área previamente alocada, sem necessidade de movimentar dados. Nesse caso, o valor devolvido pela função será idêntico ao valor original do ponteiro recebido. Se o tamanho da área apontada tiver sido aumentado, a área de dados original terá o conteúdo preservado, mas o conteúdo da área que lhe foi acrescentada será indefinido (cabendo a você preenchê-lo com valores conhecidos antes de começar a usá-lo como fonte de informação).
2) A função não consegue alterar o tamanho da área já alocada, mas consegue arranjar outra região de memória com tamanho suficiente para acomodar o novo tamanho dos dados. Nesse caso, os dados apontados pelo valor original do ponteiro são copiados para a nova região de memória, do seguinte modo: se a nova área for menor que a antiga, os últimos elementos da área original não são copiados; e a nova área for maior, todos os elementos da antiga são copiados, e o espaço excedente fica com conteúdo indefinido. A região de memória anterior é liberada para uso futuro em outras operações de (re)alocação, e o valor retornado pela função é um ponteiro para a nova área;
3) A função não consegue nenhum espaço para acomodar a quantidade de memória solicitada. Nesse caso, todos os dados apontados pelo ponteiro original continuam íntegras e disponíveis para uso, como se a realocação não tivesse sequer sido tentada. Nesse caso, a função devolve um ponteiro nulo (
NULL ).
O erro da sua realocação é que ela não prevê a possibilidade (3). Ao atribuir o valor de retorno de
realloc () à mesma variável cujo valor você usou como ponteiro a ser realocado, você perde a referência à área que terá sido preservada caso a realocação venha a falhar.
A forma correta de fazer seria mais ou menos a seguinte.
void *new_ptr;
new_ptr=realloc(ptr, new_desired_size * sizeof *ptr);
if(new_ptr){
ptr=new_ptr;
ptr_size=new_desired_size;
}
else{
// Trata o erro de alocação. Dependendo da aplicação,
// o melhor tratamento pode ser tão-somente continuar
// usando o ponteiro anterior.
}
// Usa ptr, ptr[n] e ptr_size.
---------
(*) Antes desse padrão, versões primordiais de
malloc () devolviam um valor do tipo
char * , que era o tipo de ponteiro mais genérico até então. Muitos dos compiladores da época não ligavam muito para isso, pois havia muito menos opções de diagnóstico de conversões entre ponteiros ou ponteiros e inteiros do que nos compiladores atuais. Havia, contudo, ferramentas especializadas, tais como o
lint e o
pcc , dedicadas a localizar bugs em potencial e possíveis problemas de portabilidade, e elas alarmavam quando se tentava uma conversão implícita entre tipos de ponteiros distintos. Isso, em parte, foi um dos motivos para que algo como
void * , que é um “ponteiro para qualquer coisa” fosse criado.