Por que este código falha em segfault na arquitetura de 64 bits, mas funciona bem em 32 bits?

112

Encontrei o seguinte quebra-cabeça C:

P: Por que o seguinte programa falha em segfault no IA-64, mas funciona bem no IA-32?

  int main()
  {
      int* p;
      p = (int*)malloc(sizeof(int));
      *p = 10;
      return 0;
  }

Eu sei que o tamanho de intem uma máquina de 64 bits pode não ser o mesmo que o tamanho de um ponteiro ( intpode ser de 32 bits e o ponteiro pode ser de 64 bits). Mas não tenho certeza de como isso se relaciona com o programa acima. Alguma ideia?

usuário7
fonte
50
É algo bobo como stdlib.hnão ser incluído?
user786653
3
Este código funciona bem na minha máquina de 64 bits. Ele até compila sem avisos se você #include stdlib.h(para malloc)
mpenkov
1
D'oh! @ user786653 acertou em cheio na parte importante. Com #include <stdlib.h>, é perfeitamente encontrado, mas isso não está em questão.
8
@delnan - não precisa funcionar assim, pode falhar legitimamente em uma plataforma onde sizeof(int) == sizeof(int*), por exemplo, os ponteiros são retornados por meio de um registro diferente para ints na convenção de chamada usada.
Flexo
7
Em um ambiente C99, o compilador deve dar a você pelo menos um aviso sobre a declaração implícita de malloc(). GCC diz: warning: incompatible implicit declaration of built-in function 'malloc'também.
Jonathan Leffler

Respostas:

130

A conversão para int*mascara o fato de que, sem o apropriado, #includeo tipo de retorno de mallocé assumido como int. Acontece que IA-64 tem, o sizeof(int) < sizeof(int*)que torna este problema óbvio.

(Observe também que, por causa do comportamento indefinido, ele ainda pode falhar mesmo em uma plataforma onde sizeof(int)==sizeof(int*)é verdadeiro, por exemplo, se a convenção de chamada usou registros diferentes para retornar ponteiros do que inteiros)

O FAQ comp.lang.c tem uma entrada discutindo por quemalloc converter o retorno de nunca é necessário e é potencialmente ruim .

Flexo
fonte
5
sem o #include adequado, por que o tipo de retorno de malloc é considerado um int?
user7
11
@WTP - que é um bom motivo para sempre usar newem C ++ e sempre compilar C com um compilador C e não um compilador C ++.
Flexo
6
@ user7 - essas são as regras. Qualquer tipo de retorno será considerado intse não for conhecido
Flexo
2
@vlad - a melhor ideia é sempre declarar funções ao invés de confiar em declarações implícitas exatamente por esse motivo. (E não lançar o retorno de malloc)
Flexo
16
@ user7: "temos um ponteiro p (de tamanho 64) que aponta para 32 bits de memória" - errado. O endereço do bloco alocado por malloc foi retornado de acordo com a convenção de chamada para a void*. Mas o código de chamada pensa que a função retorna int(já que você optou por não dizer o contrário), então ele tenta ler o valor de retorno de acordo com a convenção de chamada para um int. Portanto p, não necessariamente aponta para a memória alocada. Acontece que funcionou para IA32 porque um inte um void*são do mesmo tamanho e retornados da mesma maneira. Em IA64 você obtém o valor errado.
Steve Jessop
33

Provavelmente porque você não está incluindo o arquivo de cabeçalho para malloce, embora o compilador normalmente avise sobre isso, o fato de que você está convertendo explicitamente o valor de retorno significa que você está dizendo a ele que sabe o que está fazendo.

Isso significa que o compilador espera que um intseja retornado do mallocqual ele então se transforma em um ponteiro. Se eles forem de tamanhos diferentes, você ficará triste.

É por isso que você nunca lança o mallocreturn em C. O void*que ele retorna será convertido implicitamente em um ponteiro do tipo correto (a menos que você não tenha incluído o cabeçalho, caso em que provavelmente teria avisado sobre o int- potencialmente inseguro conversão para ponteiro).

paxdiablo
fonte
desculpe por soar ingênuo, mas sempre assumi que malloc retorna um ponteiro void que pode ser convertido para um tipo apropriado. Não sou um programador C e, portanto, gostaria de receber mais detalhes.
user7
5
@ user7: sem o #include <stdlib.h>, o compilador C assume que o valor de retorno de malloc é um int.
Sashang
4
@ user7: O ponteiro void pode ser lançado, mas não é necessário em C, pois void *pode ser convertido para qualquer outro tipo de ponteiro implicitamente. int *p = malloc(sizeof(int))funciona se o protótipo adequado está no escopo e falha se não estiver (porque então o resultado é considerado como estando int). Com o elenco, ambos seriam compilados e o último resultaria em erros quando sizeof(int) != sizeof(void *).
2
@ user7 Mas se você não incluir stdlib.h, o compilador não sabe malloce nem o seu tipo de retorno. Portanto, é apenas assumido intcomo padrão.
Christian Rau,
10

É por isso que você nunca compila sem avisos sobre protótipos ausentes.

É por isso que você nunca lança o retorno malloc em C.

O elenco é necessário para compatibilidade com C ++. Há pouca razão (leia: nenhuma razão aqui) para omiti-lo.

A compatibilidade com C ++ nem sempre é necessária e, em alguns casos, nem sempre é possível, mas na maioria dos casos é facilmente alcançada.

cara curioso
fonte
22
Por que diabos eu me importaria se meu código C é "compatível" com C ++? Não me importo se é compatível com perl ou java ou Eiffel ou ...
Stephen Canon
4
Se você garantir que alguém no futuro não vai olhar para o seu código C e começar, hey, vou compilá-lo com um compilador C ++ porque isso deve funcionar!
Steven Lu
3
Isso porque a maior parte do código C pode ser trivialmente compatível com C ++.
curiousguy