В частности, что опасно для кастования результата malloc?
Теперь, прежде чем люди начнут отмечать этот дубликат, я прочитал все следующее, ни один из которых не дает ответ, который я ищу:
Как в C-FAQ, так и во многих ответах на вышеупомянутые вопросы указывается таинственная ошибка, которая может скрыть возвращаемое значение malloc
; однако ни один из них не дает конкретного примера такой ошибки на практике. Теперь обратите внимание, что я сказал ошибка, а не предупреждение.
Теперь дается следующий код:
#include <string.h>
#include <stdio.h>
// #include <stdlib.h>
int main(int argc, char** argv) {
char * p = /*(char*)*/malloc(10);
strcpy(p, "hello");
printf("%s\n", p);
return 0;
}
Компиляция вышеуказанного кода с помощью gcc 4.2 с и без приведения дает те же предупреждения, и программа выполняется должным образом и дает те же результаты в обоих случаях.
[email protected]:~/$ gcc -Wextra nostdlib_malloc.c -o nostdlib_malloc
nostdlib_malloc.c: In function ‘main’:
nostdlib_malloc.c:7: warning: incompatible implicit declaration of built-in function ‘malloc’
[email protected]:~/$ ./nostdlib_malloc
hello
Так может ли кто-нибудь дать конкретный пример кода ошибки компиляции или времени выполнения, которая может возникнуть из-за литья возвращаемого значения malloc
, или это просто городская легенда?
Изменить Я столкнулся с двумя хорошо написанными аргументами в отношении этой проблемы:
Ответы
Ответ 1
Вы не получите ошибку компилятора, но предупреждение компилятора. В качестве источников, которые вы цитируете (особенно первый), вы можете получить непредсказуемую ошибку времени выполнения при использовании броска без включения stdlib.h
.
Таким образом, ошибка на вашей стороне не является литой, но забывает включить stdlib.h
. Компиляторы могут предполагать, что malloc
- это функция, возвращающая int
, поэтому преобразование указателя void*
, фактически возвращаемого malloc
в int
, а затем к вашему типу указателя из-за явного приведения. На некоторых платформах int
и указатели могут занимать различное количество байтов, поэтому преобразования типов могут привести к повреждению данных.
К счастью, современные компиляторы дают предупреждения, указывающие на вашу фактическую ошибку. См. Вывод gcc
, который вы предоставили: он предупреждает вас о том, что неявное объявление (int malloc(int)
) несовместимо со встроенным malloc
. Значит, gcc
знает malloc
даже без stdlib.h
.
Оставляя бросок, чтобы предотвратить эту ошибку, в основном те же рассуждения, что и запись
if (0 == my_var)
вместо
if (my_var == 0)
так как последний может привести к серьезной ошибке, если вы путаете =
и ==
, тогда как первая приведет к ошибке компиляции. Я лично предпочитаю последний стиль, так как он лучше отражает мое намерение, и я не склонны делать эту ошибку.
То же самое верно для того, чтобы отличить значение, возвращаемое malloc
: я предпочитаю быть явным в программировании, и я обычно дважды проверяю, чтобы включать файлы заголовков для всех функций, которые я использую.
Ответ 2
Один из хороших аргументов более высокого уровня против кастования результата malloc
часто остается без изменений, хотя, на мой взгляд, он более важен, чем известные проблемы нижнего уровня (например, усечение указателя, когда декларация отсутствует).
Хорошая практика программирования заключается в написании кода, который как можно более независим от типа. Это означает, в частности, что имена типов должны упоминаться в коде как можно меньше или вообще не упоминаться вообще. Это относится к листам (избегать ненужных бросков), типам в качестве аргументов sizeof
(избегайте использования имен типов в sizeof
) и, как правило, всех других ссылок на имена типов.
Имена типов относятся к объявлениям. Как можно больше, имена типов должны быть ограничены объявлениями и только декларациями.
С этой точки зрения этот бит кода плохой
int *p;
...
p = (int*) malloc(n * sizeof(int));
и это намного лучше
int *p;
...
p = malloc(n * sizeof *p);
не просто потому, что он "не передает результат malloc
", а скорее потому, что он не зависит от типа (или type-agnositic, если вы предпочитаете), потому что он автоматически настраивается на любой тип p
объявляется без вмешательства пользователя.
Ответ 3
Предполагается, что непрототируемые функции возвращают int
.
Итак, вы накладываете int
на указатель. Если указатели более широкие, чем int
на вашей платформе, это очень рискованное поведение.
Плюс, конечно, что некоторые люди считают предупреждения ошибками, т.е. код должен компилироваться без них.
Лично я считаю, что тот факт, что вам не нужно использовать void *
для другого типа указателя, является функцией на C и рассматривает код, который должен быть сломан.
Ответ 4
Если вы сделаете это при компиляции в 64-битном режиме, возвращаемый указатель будет усечен до 32 бит.
EDIT:
Извините за слишком короткий. Здесь фрагмент кода примера для обсуждения.
main()
{
char * c = (char *)malloc(2) ;
printf("%p", c) ;
}
Предположим, что возвращенный указатель кучи является чем-то большим, чем то, что представляется в int, скажем 0xAB00000000.
Если malloc не прототипирован для возврата указателя, возвращаемое значение int первоначально будет в каком-либо регистре со всеми значимыми битами. Теперь компилятор говорит: "Хорошо, как я конвертирую и int в указатель". Это будет либо расширение знака, либо нулевое расширение 32-битного разряда младшего порядка, о котором было сказано, что malloc "возвращает", опуская прототип. Поскольку int подписан, я думаю, что преобразование будет расширением знака, которое в этом случае преобразует значение в ноль. С возвращаемым значением 0xABF0000000 вы получите ненулевой указатель, который также вызовет некоторую забаву, когда вы попытаетесь разыменовать его.
Ответ 5
Правило многоразового использования:
В случае написания встроенной функции, в которой используется функция malloc(), чтобы сделать ее повторно используемой для кода на С++, выполните явное литье типов (например, (char *)); иначе компилятор будет жаловаться.
Ответ 6
Указатель void в C может быть назначен любому указателю без явного приведения. Компилятор выдаст предупреждение, но он может быть повторно использован в С++ по типу malloc()
для соответствующего типа. Кроме того, его можно использовать в C, потому что C не является строгой проверкой типов. Но С++ строго проверяет тип, поэтому вам нужно ввести cast malloc()
в С++.