¿Por qué este código es predeterminado en la arquitectura de 64 bits pero funciona bien en 32 bits?

112

Me encontré con el siguiente acertijo C:

P: ¿Por qué el siguiente programa se produce por defecto en IA-64, pero funciona bien en IA-32?

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

Sé que el tamaño de inten una máquina de 64 bits puede no ser el mismo que el tamaño de un puntero ( intpodría ser de 32 bits y el puntero podría ser de 64 bits). Pero no estoy seguro de cómo se relaciona esto con el programa anterior. ¿Algunas ideas?

usuario7
fuente
50
¿Es algo tonto como stdlib.hno estar incluido?
user786653
3
Este código funciona bien en mi máquina de 64 bits. Incluso se compila sin advertencias si usted #include stdlib.h(para malloc)
mpenkov
1
¡Oh! @ user786653 clavó la parte importante. Con #include <stdlib.h>, se encuentra perfectamente, pero esa no es la cuestión.
8
@delnan: sin embargo, no tiene que funcionar así, podría fallar legítimamente en una plataforma donde sizeof(int) == sizeof(int*), por ejemplo, los punteros se devuelven a través de un registro diferente a ints en la convención de llamada utilizada.
Flexo
7
En un entorno C99, el compilador debería darle al menos una advertencia sobre la declaración implícita de malloc(). GCC dice: warning: incompatible implicit declaration of built-in function 'malloc'también.
Jonathan Leffler

Respuestas:

130

La conversión a int*enmascara el hecho de que sin el #includetipo de retorno adecuado mallocse supone que es int. IA-64 tiene lo sizeof(int) < sizeof(int*)que hace que este problema sea obvio.

(Tenga en cuenta también que, debido al comportamiento indefinido, aún podría fallar incluso en una plataforma donde sizeof(int)==sizeof(int*)se cumple, por ejemplo, si la convención de llamada usa registros diferentes para devolver punteros que enteros)

Las preguntas frecuentes de comp.lang.c tienen una entrada que explica por qué mallocno es necesario nunca emitir el retorno de y es potencialmente malo .

Flexo
fuente
5
sin el #include adecuado, ¿por qué se supone que el tipo de retorno de malloc es un int?
usuario7
11
@WTP - que es una buena razón para usar siempre newen C ++ y siempre compilar C con un compilador de C y no con un compilador de C ++.
Flexo
6
@ user7 - esas son las reglas. Se asume que cualquier tipo de retorno es intsi no se conoce
Flexo
2
@vlad: la mejor idea es declarar siempre funciones en lugar de confiar en declaraciones implícitas exactamente por esta razón. (Y no lanzar el regreso de malloc)
Flexo
16
@ user7: "tenemos un puntero p (de tamaño 64) que apunta a 32 bits de memoria" - incorrecto. La dirección del bloque asignada por malloc se devolvió de acuerdo con la convención de llamada para a void*. Pero el código de llamada cree que la función regresa int(ya que optó por no decirlo de otra manera), por lo que intenta leer el valor de retorno de acuerdo con la convención de llamada para un int. Por plo tanto , no apunta necesariamente a la memoria asignada. Dio la casualidad de que funcionaba para IA32 porque an inty a void*son del mismo tamaño y se devolvieron de la misma manera. En IA64 obtiene un valor incorrecto.
Steve Jessop
33

Lo más probable es que se deba a que no incluye el archivo de encabezado mallocy, aunque el compilador normalmente le advierte de esto, el hecho de que esté emitiendo explícitamente el valor de retorno significa que le está diciendo que sabe lo que está haciendo.

Eso significa que el compilador espera intque se devuelva un desde el mallocque luego lanza a un puntero. Si son de diferentes tamaños, eso te causará dolor.

Esta es la razón por la que nunca emite el mallocretorno en C.El void*que devuelve se convertirá implícitamente en un puntero del tipo correcto (a menos que no haya incluido el encabezado, en cuyo caso probablemente le habría advertido de la información potencialmente insegura). conversión a puntero).

paxdiablo
fuente
perdón por sonar ingenuo, pero siempre asumí que malloc devuelve un puntero vacío que se puede convertir en un tipo apropiado. No soy un programador de C y, por lo tanto, agradecería un poco más de detalles.
usuario7
5
@ user7: sin #include <stdlib.h>, el compilador de C asume que el valor de retorno de malloc es un int.
Sashang
4
@ user7: El puntero vacío se puede convertir, pero no es necesario en C, ya que void *se puede convertir implícitamente a cualquier otro tipo de puntero. int *p = malloc(sizeof(int))funciona si el prototipo adecuado está dentro del alcance y falla si no lo está (porque entonces se supone que el resultado es int). Con el elenco, ambos se compilarían y el último daría lugar a errores cuando sizeof(int) != sizeof(void *).
2
@ user7 Pero si no lo incluye stdlib.h, el compilador no sabe mallocni su tipo de retorno. Así que simplemente asume intcomo predeterminado.
Christian Rau
10

Es por eso que nunca compila sin advertencias sobre prototipos faltantes.

Es por eso que nunca lanzas el retorno malloc en C.

El elenco es necesario para la compatibilidad con C ++. Hay pocas razones (léase: aquí no hay razón) para omitirlo.

La compatibilidad con C ++ no siempre es necesaria, y en algunos casos no es posible en absoluto, pero en la mayoría de los casos se logra muy fácilmente.

curioso
fuente
22
¿Por qué diablos me importaría si mi código C es "compatible" con C ++? No me importa si es compatible con perl o java o Eiffel o ...
Stephen Canon
4
Si garantiza que alguien en el futuro no va a mirar su código C y listo, ¡lo voy a compilar con un compilador C ++ porque eso debería funcionar!
Steven Lu
3
Eso es porque la mayoría del código C se puede hacer trivialmente compatible con C ++.
Curioso