Почему этот код имеет segfault в 64-битной архитектуре, но нормально работает в 32-битной?

112

Я наткнулся на следующую загадку C:

В: Почему следующая программа имеет segfault на IA-64, но нормально работает на IA-32?

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

Я знаю, что размер intна 64-битной машине может отличаться от размера указателя ( intможет быть 32 бита, а указатель может быть 64 бита). Но я не уверен, как это связано с указанной выше программой. Любые идеи?

user7
источник
50
Это что-то глупое, как stdlib.hотсутствие включения?
user786653
3
Этот код отлично работает на моей 64-битной машине. Он даже компилируется без предупреждений, если вы #include stdlib.h(для malloc)
mpenkov
1
Ооо! @ user786653 прибил важный момент. При том #include <stdlib.h>, что это прекрасно найти, но не в этом вопрос.
8
@delnan - он не должен работать таким образом, он может законно выйти из строя на платформе, где sizeof(int) == sizeof(int*), например, указатели были возвращены через регистр, отличный от ints в используемом соглашении о вызовах.
Flexo
7
В среде C99 компилятор должен давать вам как минимум предупреждение о неявном объявлении malloc(). GCC говорит: warning: incompatible implicit declaration of built-in function 'malloc'тоже.
Джонатан Леффлер

Ответы:

130

Приведение к int*маскирует тот факт, что без надлежащего предполагается, что #includeтип возврата . IA-64 имеет, что делает эту проблему очевидной.mallocintsizeof(int) < sizeof(int*)

(Также обратите внимание, что из-за неопределенного поведения он все равно может выйти из строя даже на платформе, где sizeof(int)==sizeof(int*)выполняется, например, если соглашение о вызовах использовало разные регистры для возврата указателей, чем целые числа)

В FAQ по comp.lang.c есть запись, в которой обсуждается, почему приведение возврата из mallocникогда не требуется и потенциально плохо .

Флексография
источник
5
без правильного #include, почему предполагается, что возвращаемый тип malloc является int?
user7
11
@WTP - это хорошая причина всегда использовать newв C ++ и всегда компилировать C с помощью компилятора C, а не компилятора C ++.
Flexo
6
@ user7 - это правила. Предполагается, что любой возвращаемый тип будет, intесли он неизвестен
Flexo
2
@vlad - лучше всегда объявлять функции, а не полагаться на неявные объявления именно по этой причине. (И не забрасывать отдачу malloc)
Flexo
16
@ user7: «у нас есть указатель p (размером 64), который указывает на 32 бита памяти» - неверно. Адрес блока, выделенного malloc, был возвращен в соответствии с соглашением о вызовах для void*. Но вызывающий код считает, что функция возвращает int(поскольку вы решили не сообщать об обратном), поэтому он пытается прочитать возвращаемое значение в соответствии с соглашением о вызовах для int. Следовательно p, не обязательно указывает на выделенную память. Так получилось, что для IA32 это сработало, потому что an intи a void*имеют одинаковый размер и возвращаются одинаково. На IA64 вы получаете неправильное значение.
Стив Джессоп,
33

Скорее всего, потому что вы не включаете файл заголовка для mallocи, хотя компилятор обычно предупреждает вас об этом, тот факт, что вы явно приводите возвращаемое значение, означает, что вы говорите ему, что знаете, что делаете.

Это означает, что компилятор ожидает, что intбудет возвращен объект , из mallocкоторого он затем преобразуется в указатель. Если они разных размеров, это вас огорчит.

Вот почему вы никогда не бросил mallocвозвращение в С. , void*что он возвращает будет неявно преобразован в указатель правильного типа (если вы не включили заголовок в этом случае он , вероятно , был бы предупрежден вас о потенциально небезопасных int- преобразование в указатель).

paxdiablo
источник
извините за наивность, но я всегда предполагал, что malloc возвращает указатель void, который может быть приведен к соответствующему типу. Я не программист на C и поэтому был бы признателен за более подробную информацию.
user7
5
@ user7: без #include <stdlib.h> компилятор C предполагает, что возвращаемое значение malloc является int.
sashang
4
@ user7: указатель void может быть приведен, но он не нужен в C, так как его void *можно неявно преобразовать в любой другой тип указателя. int *p = malloc(sizeof(int))работает, если правильный прототип находится в области видимости, и терпит неудачу, если нет (потому что тогда предполагается результат int). С приведением оба будут компилироваться, и последнее приведет к ошибкам, когда sizeof(int) != sizeof(void *).
2
@ user7 Но если вы не включите stdlib.h, компилятор не знает, как mallocи его возвращаемый тип. Так что это просто предполагается intпо умолчанию.
Christian Rau
10

Вот почему вы никогда не компилируете без предупреждения об отсутствии прототипов.

Вот почему вы никогда не выполняете возврат malloc в C.

Приведение необходимо для совместимости с C ++. Есть небольшая причина (читай: здесь нет причин) пропустить ее.

Совместимость с C ++ не всегда нужна, а в некоторых случаях вообще невозможна, но в большинстве случаев это очень легко достигается.

curiousguy
источник
22
С какой стати меня волновать, "совместим" ли мой код на C с C ++? Меня не волнует, совместим ли он с Perl, java, Eiffel или ...
Стивен Кэнон
4
Если вы гарантируете, что кто-то в дальнейшем не будет смотреть на ваш код C и уходить, я собираюсь скомпилировать его с помощью компилятора C ++, потому что это должно работать!
Стивен Лу
3
Это потому, что большую часть кода C можно тривиально сделать совместимым с C ++.
curiousguy