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.