Pular para o conteúdo principal

Porting C to ... C

Chegou, então, a hora de ressuscitar um programa desenvolvido pelo fundador da empresa em um inominável sistema UNIX rodando em i386 no passado remoto. O programa, cujo sistema de construção permitia a configuração para inúmeras variantes do UNIX de então, permaneceu compilável com mínimas modificações até o Fedora 17. Impressionante.

Infelizmente, ao executar o programa, segmentation fault a torto e a direito. Com a ajuda de um debugger, alcançamos a seguinte iteração, simplificada para este artigo:

for (int i = 0; i < name_list_size; ++i)
{
  p = (char *) getenv(name_list[i]);
  if (p != NULL)
    if (*p != NULL)
      strcpy(value_list[i], name_list[i]);
}

Simples, certo? O programa levantava SIGSEGV ao avaliar a expressão *p.

Como é possível? Certamente getenv retorna um endereço válido ou NULL. Certamente p não é NULL quando avalia-se *p.

Qual é o tipo de p? char * p . Por acaso o programa está sobrescrevendo memória do sistema inadvertidamente?

Num ato de desespero, escrevi o seguinte programa:

#include

int main (int argc, char * argv [])
{
  char * p = getenv("SHELL");
  return 0;
}

que obviamente não deve levantar SIGSEGV certo? Pois este programa também falha com SIGSEGV.

A dica estava no alerta do compilador: conversão de int para char * causa possível truncamento do valor.

Ora, porque haveria uma conversão de int para ponteiro naquela sentença? Sabemos que getenv retorna char *.

Não, calma. Onde está declarada getenv? Em stdlib.h. Eu confundi.

Ora, como pode compilar este código se não há declaração para getenv? Ah, sim; para o C, na ausência de uma declaração, assume-se que foo é int foo (...).

Por que isso causaria um crash? Por que esta máquina é x86_64 e um ponteiro é maior que um int. Portanto, o valor retornado por getenv estava truncado para int e posteriormente estendido para ponteiro.

O programa funcionava por sorte já que nunca encontrou uma máquina onde sizeof(int) != sizeof(void *).

Comentários

Postagens mais visitadas deste blog

return void();

É uma pequeneza mas eu gostaria muito que as linguagens da família do C permitissem retornar void quando a função retorna void . Escrever da forma a seguir me aborrece. public class Foo  {    public void log (String msg) { }    public void bar (String x, String y) { }    public void bar (String x)    {      if (x == null)      {        log("x was null");        return;      }      bar(x, "default");    } } Eu ficaria mais feliz escrevendo assim: public class Foo  {    public void log (String msg) { }    public void bar (String x, String y) { }    public void bar (String x)    {      if (x == null) return log("x was null");      bar(x, "default");     } }

Por que goto é considerado prejudicial?

Recentemente, o Fabiano Vasconcelos abriu uma discussão no Grupo de Usuários de C e C++: De cara eu vi algo aqui um pouco estranho, se que o amigo Márcio me permite comentar: que muitos programadores, inclusive eu (se que posso ser rotulado como programador) foram instruídos com o princípio de NUNCA usar o goto, por ser considerado um mau estilo de programação. Durante a discussão, o Eduardo Vieira puxou um artigo da KernelTrap sobre uma discussão similar ocorrida no grupo de desenvolvimento do Linux, onde Robert Wilken disse o seguinte: In general, if you can structure your code properly, you should never need a goto, and if you don't need a goto you shouldn't use it. It's just "common sense" as I've always been taught. Unless you're intentionally trying to write code that's harder for others to read. É preciso colocar a máxima "goto considered harmful" na perspectiva histórica adequada. Acredito que praticamente todo programador trein...