Ненужные указатели-броски в C - PullRequest
16 голосов
/ 20 сентября 2008

Я получил комментарий к моему ответу на эту тему:

Похоже, что Malloc внутри вызова функции освобождается при возврате?

Короче у меня был такой код:

int * somefunc (void)
{
  int * temp = (int*) malloc (sizeof (int));
  temp[0] = 0;
  return temp;
}

Я получил этот комментарий:

Могу я просто сказать, пожалуйста, не разыгрывайте возвращаемое значение malloc? Это не требуется и может скрывать ошибки.

Я согласен, что приведение не требуется в C. Это обязательно в C ++, поэтому я обычно добавляю их на тот случай, если мне придется портировать код на C ++ однажды.

Однако мне интересно, как такие приведения могут скрывать ошибки. Есть идеи?

Edit:

Похоже, с обеих сторон есть очень хорошие и веские аргументы. Спасибо за публикацию, ребята.

Ответы [ 12 ]

17 голосов
/ 20 сентября 2008

Кажется уместным, я отправляю ответ, так как я оставил комментарий: P

Обычно, если вы забудете включить stdlib.h, компилятор примет malloc, возвращающее int. Без кастинга вы получите предупреждение. С кастингом вы не будете.

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

Об этом много написано, быстрый поиск в Google приведет к более подробным объяснениям.

1012 * редактировать * Утверждалось, что TYPE * p; p = (TYPE *)malloc(n*sizeof(TYPE)); делает это очевидным, когда вы случайно не выделяете достаточно памяти, потому что, скажем, вы думали, что p было TYPe, а не TYPE, и, таким образом, мы должны приводить malloc, потому что преимущество этого метода отменяет меньшую стоимость случайно подавление предупреждений компилятора. Я бы хотел отметить две вещи: вы должны написать p = malloc(sizeof(*p)*n);, чтобы всегда гарантировать, что вы выделите нужное количество места При вышеупомянутом подходе вам нужно внести изменения в 3 местах, если вы когда-либо изменили тип p: один раз в объявлении, один раз в malloc и один раз в приведении. Короче говоря, я все еще лично верю, что нет необходимости приводить возвращаемое значение malloc, и это определенно не лучшая практика.

11 голосов
/ 20 сентября 2008

Этот вопрос помечен как для C, так и для C ++, поэтому у него есть как минимум два ответа, ИМХО:

C

Гм ... Делай, что хочешь.

Я полагаю, что приведенная выше причина «Если вы не включите« stdlib », то вы не получите предупреждение», не является допустимой, потому что не следует полагаться на такого рода хаки, чтобы не забыть включить заголовок .

Истинная причина, по которой вы можете не написать приведение, состоит в том, что компилятор C уже бесшумно преобразовал void * в любой тип указателя, который вы хотите, и поэтому делать это самостоятельно излишне и бесполезно.

Если вы хотите иметь безопасность типов, вы можете либо переключиться на C ++, либо написать свою собственную функцию-оболочку, например:

int * malloc_Int(size_t p_iSize) /* number of ints wanted */
{
   return malloc(sizeof(int) * p_iSize) ;
}

C ++

Иногда, даже в C ++, вам нужно извлекать выгоду из утилит malloc / realloc / free. Тогда вам придется сыграть. Но ты уже знал это. Использование static_cast <> (), как всегда, будет лучше, чем приведение в стиле 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 ++ у вас теперь нет оправданий, если кто-то находит в вашем коде более одного C malloc ... : -)

C + C ++ кроссовер

Иногда вы хотите создать код, который будет компилироваться как в C, так и в C ++ (по каким-то причинам ... Разве это не точка блока C ++ extern "C" {}?). В этом случае C ++ требует приведение, но C не будет понимать ключевое слово static_cast, поэтому решение - приведение в стиле C (которое все еще допустимо в C ++ именно по этим причинам).

Обратите внимание, что даже при написании чистого кода на C его компиляция с помощью компилятора C ++ приведет к гораздо большему количеству предупреждений и ошибок (например, попытка использовать функцию без объявления ее вначале не будет компилироваться, в отличие от ошибки, упомянутой выше) .

Итак, чтобы быть в безопасности, напишите код, который будет аккуратно компилироваться в C ++, изучите и исправьте предупреждения, а затем используйте компилятор C для создания окончательного двоичного файла. Это значит, опять же, написать актерский состав в стиле C.

7 голосов
/ 20 сентября 2008

Одна из возможных ошибок, которую он может внести, - это если вы компилируете в 64-битной системе с использованием C (не C ++).

Обычно, если вы забудете включить stdlib.h, будет применяться правило int по умолчанию. Таким образом, компилятор с радостью предположит, что malloc имеет прототип int malloc();. Во многих 64-разрядных системах int является 32-разрядным, а указатель - 64-разрядным.

О-о, значение усекается, и вы получаете только младшие 32-битные указатели! Теперь, если вы приведете возвращаемое значение malloc, эта ошибка будет скрыта приведением. Но если вы этого не сделаете, вы получите ошибку (что-то вроде «не может преобразовать int в T *»).

Это, конечно, не относится к C ++ по двум причинам. Во-первых, у него нет правила int по умолчанию, во-вторых, требуется приведение.

В общем, вы все равно должны быть новыми в коде c ++: -P.

6 голосов
/ 20 сентября 2008

Ну, я думаю, что это полная противоположность - всегда напрямую приводите его к нужному типу. Читайте здесь!

2 голосов
/ 20 сентября 2008

Аргумент "забыл stdlib.h" - соломенный человек. Современные компиляторы будут обнаруживать и предупреждать о проблеме (gcc -Wall).

Вы всегда должны немедленно приводить результат malloc. Невыполнение этого требования должно рассматриваться как ошибка, и не только потому, что она потерпит неудачу как C ++. Например, если вы нацелены на архитектуру машины с разными указателями, вы можете получить очень хитрую ошибку, если не добавите в актерский состав.

Редактировать : Комментатор Эван Теран правильный. Моя ошибка заключалась в том, что компилятору не нужно было выполнять какую-либо работу с пустым указателем в любом контексте. Я волнуюсь, когда думаю об ошибках FAR-указателя, поэтому моя интуиция - бросить все. Спасибо Эван!

1 голос
/ 10 февраля 2010

Люди уже привели причины, по которым я обычно выдвигаюсь: старый (больше не применимый к большинству компиляторов) аргумент о том, что не следует включать stdlib.h и использовать sizeof *p, чтобы убедиться, что типы и размеры всегда совпадают независимо от последующего обновления. Я хочу указать еще один аргумент против кастинга. Это маленький, но я думаю, что это применимо.

C довольно слабо типизирован. Большинство безопасных преобразований типов происходят автоматически, а большинство небезопасных требуют преобразования. Рассмотрим:

int from_f(float f)
{
    return *(int *)&f;
}

Это опасный код. Это технически неопределенное поведение, хотя на практике это будет делать то же самое почти на каждой платформе, на которой вы его используете. И актерский состав помогает вам «Этот код - ужасный взлом».

Рассмотрим:

int *p = (int *)malloc(sizeof(int) * 10);

Я вижу актеров и задаюсь вопросом: «Зачем это нужно? Где взлом?» На моей шее поднимаются волосы, что происходит что-то плохое, хотя на самом деле код совершенно безвреден.

Пока мы используем C, приведение (особенно приведение указателей) является способом сказать: «Здесь происходит что-то злое и легко разрушаемое». Они могут выполнить то, что вам нужно, но они указывают вам и будущим сопровождающим, что с детьми не все в порядке.

Использование приведений к каждому malloc уменьшает показатель "взлома" приведения указателя. Это делает менее резким видеть такие вещи, как *(int *)&f;.

Примечание: C и C ++ - это разные языки. C слабо типизирован, C ++ более типизирован. Приведения * необходимы в C ++ , хотя они вообще не указывают на взлом из-за (по моему скромному мнению) излишне сильной системы типов C ++. (Действительно, этот конкретный случай - единственное место, где я думаю, что система типов C ++ «слишком сильная», но я не могу представить ни одного места, где она «слишком слабая», что делает ее в целом слишком сильной для моих вкусов.)

Если вы беспокоитесь о совместимости с C ++, не надо. Если вы пишете C, используйте компилятор C. Есть много действительно хороших доступных для каждой платформы. Если по какой-то глупой причине у вас есть для написания кода на C, который компилируется чисто как C ++, вы на самом деле не пишете C. Если вам нужно перенести C на C ++, вы должны внести множество изменений в сделайте ваш код C более идиоматическим C ++.

Если вы не можете сделать что-либо из этого, ваш код не будет красивым, независимо от того, что вы делаете, поэтому не имеет значения, как вы решите выполнять приведение в этот момент. Мне нравится идея использования шаблонов для создания нового распределителя, который возвращает правильный тип, хотя это просто переизобретает ключевое слово new.

1 голос
/ 28 сентября 2008
#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);
1 голос
/ 20 сентября 2008

На самом деле, единственный способ, которым приведение может скрыть ошибку, - это если вы конвертировали один тип данных в меньший тип данных и потеряли данные, или если вы конвертировали груши в яблоки. Возьмите следующий пример:

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 (значение левая сторона задания) все равно не может взять.

0 голосов
/ 20 сентября 2008

Я думаю, вы должны поставить актерский состав. Учтите, что для типов есть три локации:

T1 *p;
p = (T2*) malloc(sizeof(T3));

Две строки кода могут быть широко разделены. Поэтому хорошо, что компилятор обеспечит , что T1 == T2. Проще визуально проверить, что T2 == T3.

Если вы пропустили бросок T2, то вам следует надеяться, что T1 == T3.

С другой стороны, у вас отсутствует аргумент stdlib.h, но я думаю, что это менее вероятно, будет проблемой.

0 голосов
/ 20 сентября 2008

С другой стороны, если вам когда-либо понадобится перенести код на C ++, гораздо лучше использовать оператор 'new'.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...