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?


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 .


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).


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.


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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.