Не нужно использовать указатели в C
Я получил комментарий к моему ответу в этой теме:
Malloc внутри вызова функции, кажется, освобождается по возвращении?
Короче, у меня был код:
int * somefunc (void)
{
int * temp = (int*) malloc (sizeof (int));
temp[0] = 0;
return temp;
}
Я получил этот комментарий:
Могу я просто сказать, пожалуйста, не бросайте возвращаемое значение malloc? Это не требуется и может скрывать ошибки.
Я согласен с тем, что в C не требуется отрисовка. Это обязательное условие в С++, поэтому я обычно добавляю их на всякий случай, когда мне нужно в следующий раз выгрузить код на С++.
Однако мне интересно, как подобные приведения могут скрыть ошибки. Любые идеи?
Изменить:
Похоже, что с обеих сторон есть очень хорошие и веские аргументы. Спасибо за публикацию, ребята.
Ответы
Ответ 1
Кажется подходящим я отправляю ответ, так как я оставил комментарий: P
В принципе, если вы забудете включить stdlib.h
, то компилятор примет malloc
возвращает int
. Без кастинга вы получите предупреждение. С кастингом вы не будете.
Таким образом, вы не получаете ничего и не рискуете подавлять законные предупреждения.
Об этом написано много, быстрый поиск в Google будет содержать более подробные объяснения.
изменить
Утверждалось, что
TYPE * p;
p = (TYPE *)malloc(n*sizeof(TYPE));
делает это очевидным, когда вы случайно не выделяете достаточное количество памяти, потому что говорите, что вы считали p
TYPe
not TYPe
, и поэтому мы должны использовать malloc, потому что преимущество этого метода переопределяет меньшую стоимость случайного подавление предупреждений компилятора.
Я хотел бы указать на 2 вещи:
- вы должны написать
p = malloc(sizeof(*p)*n);
, чтобы всегда гарантировать, что вы malloc нужное количество пространства
- с помощью вышеуказанного подхода вам нужно внести изменения в 3-х местах, если вы когда-либо изменили тип
p
: один раз в объявлении, один раз в malloc
и один раз в броске.
Короче говоря, я по-прежнему лично считаю, что нет необходимости выдавать возвращаемое значение malloc
, и это, безусловно, не самая лучшая практика.
Ответ 2
Этот вопрос помечен как для C, так и для С++, поэтому он имеет как минимум два ответа: IMHO:
С
Ах... Делай все, что хочешь.
Я считаю причину, указанную выше. "Если вы не включаете" stdlib ", то вы не получите предупреждение" не является допустимым, потому что не следует полагаться на такие хаки, чтобы не забывать включать заголовок.
Настоящая причина, по которой вы можете не писать актерский состав, заключается в том, что компилятор C уже безмолвно прикладывает void *
к любому типу указателя, который вы хотите, и поэтому, делая это самостоятельно, это излишний и бесполезный.
Если вы хотите иметь безопасность типа, вы можете переключиться на С++ или написать свою собственную функцию-обертку, например:
int * malloc_Int(size_t p_iSize) /* number of ints wanted */
{
return malloc(sizeof(int) * p_iSize) ;
}
С++
Иногда даже на С++ вы должны получать прибыль от malloc/realloc/free utils. Тогда вам придется бросить. Но ты это уже знал. Использование static_cast < gt;() будет, как всегда, лучше, чем приведение в стиле C.
И в C вы можете переопределить malloc (и realloc и т.д.) с помощью шаблонов для обеспечения безопасности типов:
template <typename T>
T * myMalloc(const size_t p_iSize)
{
return static_cast<T *>(malloc(sizeof(T) * p_iSize)) ;
}
Который будет использоваться как:
int * p = myMalloc<int>(25) ;
free(p) ;
MyStruct * p2 = myMalloc<MyStruct>(12) ;
free(p2) ;
и следующий код:
// error: cannot convert ‘int*’ to ‘short int*’ in initialization
short * p = myMalloc<int>(25) ;
free(p) ;
не будет компилироваться, поэтому нет проблем.
В целом, в чистом С++, теперь у вас нет оправдания, если кто-то находит в вашем коде более одного C malloc...
: -)
C + С++ crossover
Иногда вы хотите создать код, который будет компилироваться как на C, так и на С++ (по каким-либо причинам... Разве это не точка блока С++ extern "C" {}
?). В этом случае С++ требует приведения, но C не будет понимать ключевое слово static_cast, поэтому решение является приложением C-стиля (которое по-прежнему является законным в С++ именно по таким причинам).
Обратите внимание, что даже при написании чистого C-кода компиляция с помощью компилятора С++ даст вам гораздо больше предупреждений и ошибок (например, попытка использовать функцию без объявления ее сначала не будет компилироваться, в отличие от упомянутой выше ошибки).
Итак, чтобы быть в безопасности, напишите код, который будет скомпилирован на С++, изучите и исправьте предупреждения, а затем используйте компилятор C для создания финального двоичного файла. Это означает, что, опять же, напишите актерский состав в стиле C-стиля.
Ответ 3
Возможна одна возможная ошибка, если вы компилируете в 64-разрядной системе, используя C (не С++).
В принципе, если вы забудете включить stdlib.h
, применяется правило int по умолчанию. Таким образом, компилятор с радостью предположит, что malloc
имеет прототип int malloc();
. Во многих 64-битных системах int 32-бит, а указатель - 64-разрядный.
Uh oh, значение становится усеченным, и вы получаете только младшие 32-битные указатели! Теперь, если вы отбрасываете возвращаемое значение malloc
, эта ошибка скрывается приложением. Но если вы этого не сделаете, вы получите ошибку (что-то к природе "не может преобразовать int в T *" ).
Это не относится к С++, конечно, по двум причинам. Во-первых, он не имеет правила по умолчанию int, во-вторых, он требует приведения.
В общем, вы все равно должны просто написать новый код С++: -P.
Ответ 4
Хорошо, я думаю, что это полная противоположность - всегда прямо бросать его на необходимый тип. Читайте здесь!
Ответ 5
Аргумент "забыл stdlib.h" - соломенный человек. Современные компиляторы обнаружат и предупреждают о проблеме (gcc -Wall).
Вы всегда должны сразу выводить результат malloc. Не делать этого следует считать ошибкой, а не только потому, что он не будет работать как С++. Например, если вы ориентируетесь на архитектуру компьютера с различными типами указателей, вы можете столкнуться с очень сложной ошибкой, если не станете отличать.
Изменить: комментарий Evan Teran правильный. Моя ошибка заключалась в том, что компилятору не нужно было работать с указателем пустоты в любом контексте. Ужасно, когда я думаю о ошибках указателя FAR, поэтому моя интуиция - это бросить все. Спасибо Эван!
Ответ 6
Собственно, единственный способ, с помощью которого приведение может скрыть ошибку, - это преобразование из одного типа данных в меньший тип данных и потерянные данные или преобразование груш в яблоки. Возьмем следующий пример:
int int_array[10];
/* initialize array */
int *p = &(int_array[3]);
short *sp = (short *)p;
short my_val = *sp;
в этом случае преобразование в short должно было бы отбросить некоторые данные из int. И тогда этот случай:
struct {
/* something */
} my_struct[100];
int my_int_array[100];
/* initialize array */
struct my_struct *p = &(my_int_array[99]);
в котором вы должны указывать на неправильный тип данных или даже на недопустимую память.
Но в целом, и если вы знаете, что делаете, это нормально делать кастинг. Тем более, когда вы получаете память из malloc, которая, случается, возвращает указатель void, который вы не можете использовать вообще, если вы его не произнесете, и большинство компиляторов предупредит вас, если вы накладываете на что-то значение lvalue (значение для левая сторона задания) не может в любом случае.
Ответ 7
#if CPLUSPLUS
#define MALLOC_CAST(T) (T)
#else
#define MALLOC_CAST(T)
#endif
...
int * p;
p = MALLOC_CAST(int *) malloc(sizeof(int) * n);
или, поочередно
#if CPLUSPLUS
#define MYMALLOC(T, N) static_cast<T*>(malloc(sizeof(T) * N))
#else
#define MYMALLOC(T, N) malloc(sizeof(T) * N)
#endif
...
int * p;
p = MYMALLOC(int, n);
Ответ 8
Люди уже приводили причины, по которым я обычно выхожу: старый (более не применимый к большинству компиляторов) аргумент о том, что он не включает stdlib.h
и использует sizeof *p
, чтобы убедиться, что типы и размеры всегда совпадают независимо от последующего обновления. Я хочу указать еще один аргумент против кастинга. Это маленький, но я думаю, что это применимо.
C довольно слабо типизирован. Большинство безопасных преобразований типов происходят автоматически, а большинству небезопасных требуется бросок. Рассмотрим:
int from_f(float f)
{
return *(int *)&f;
}
Этот опасный код. Это технически поведение undefined, хотя на практике это будет делать то же самое на почти каждой платформе, на которой вы ее запускаете. И актер помогает вам сказать "Этот код - ужасный взлом".
Рассмотрим:
int *p = (int *)malloc(sizeof(int) * 10);
Я вижу актерский состав, и мне интересно: "Почему это необходимо? Где взломать?" Это поднимает волосы на моей шее, что происходит что-то злое, когда на самом деле код совершенно безвреден.
Пока мы используем C, броски (особенно стрелки) - это способ сказать: "Здесь что-то злое и легко разрушаемое". Они могут выполнить то, что вам нужно, но они указывают вам и будущим сопровождающим, что дети не в порядке.
Использование меток на каждом malloc
уменьшает индикацию "взлома" для литья указателя. Это делает его менее резким, чтобы видеть такие вещи, как *(int *)&f;
.
Примечание. C и С++ - разные языки. C слабо типизирован, С++ более строго типизирован. Броски необходимы на С++, хотя они вообще не указывают на хак, из-за (по моему скромному мнению) излишне сильной системы типа С++. (Действительно, этот конкретный случай - единственное место, где я думаю, что система типа С++ "слишком сильна", но я не могу придумать ни одного места, где она "слишком слаба", что делает ее слишком сильной для моих вкусов.)
Если вы беспокоитесь о совместимости с С++, не делайте этого. Если вы пишете C, используйте компилятор C. На каждой платформе есть много действительно хороших. Если по какой-то простой причине вам нужно написать C-код, который компилируется чисто как С++, вы на самом деле не пишете C. Если вам нужно переместить C на С++, вы должны внести много изменений, чтобы сделать ваш C-код более идиоматичным С++.
Если вы не можете этого сделать, ваш код будет неважным, независимо от того, что вы делаете, поэтому на самом деле не имеет значения, как вы решаете бросить на этом этапе. Мне нравится идея использования шаблонов для создания нового распределителя, который возвращает правильный тип, хотя это в основном просто заново изобретает ключевое слово new
.
Ответ 9
Выполнение функции, которая возвращает (void *) вместо be (int *), безвредна: вы накладываете один тип указателя на другой.
Приведение функции, возвращающей целое число, вместо этого, является указателем, скорее всего, неверным. Компилятор отметил бы, что если бы вы его явно не использовали.
Ответ 10
Одна возможная ошибка может (в зависимости от того, что вы действительно хотите или не хотите) разделить с одним масштабом по размеру и назначить указателю другого типа. Например.
int *temp = (int *)malloc(sizeof(double));
Могут быть случаи, когда вы хотите это сделать, но я подозреваю, что они редки.
Ответ 11
Я думаю, что вы должны поставить бросок. Учтите, что для типов есть три расположения:
T1 *p;
p = (T2*) malloc(sizeof(T3));
Две строки кода могут быть широко разделены. Поэтому хорошо, что компилятор применяет, что T1 == T2. Легче визуально проверить, что T2 == T3.
Если вы пропустили трансляцию T2, вам нужно надеяться, что T1 == T3.
С другой стороны, у вас есть отсутствующий аргумент stdlib.h, но я думаю, что это менее вероятно, проблема.
Ответ 12
С другой стороны, если вам когда-либо понадобится переносить код на С++, гораздо лучше использовать "новый" оператор.