Ненужные указатели-броски в C

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

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

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

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

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

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

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

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

Редактировать:

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

12 ответов

Решение

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

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

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

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

редактировать

Утверждалось, что

TYPE * p;
p = (TYPE *)malloc(n*sizeof(TYPE));

делает это очевидным, когда вы случайно не выделяете достаточно памяти, потому что, скажем, вы думали p было TYPe не TYPEи, следовательно, мы должны привести malloc, потому что преимущество этого метода перекрывает меньшую стоимость случайного подавления предупреждений компилятора.

Я хотел бы отметить 2 вещи:

  1. ты должен написать p = malloc(sizeof(*p)*n); чтобы всегда гарантировать правильное количество пространства
  2. При вышеуказанном подходе вам нужно внести изменения в 3 местах, если вы когда-либо изменили тип p: один раз в декларации, один раз в mallocи один раз в актерском составе.

Короче говоря, я до сих пор лично считаю, что нет необходимости в приведении возвращаемого значения malloc и это, конечно, не лучшая практика.

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

С

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

Я полагаю, что приведенная выше причина "Если вы не включите"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.

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

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

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

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

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

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

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

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

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

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

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

Люди уже привели причины, по которым я обычно выдвигаюсь: старый (больше не применимый к большинству компиляторов) аргумент о не включении 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 ключевое слово.

#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);

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

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

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

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

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

Преобразование функции, которая возвращает (void *) вместо взамен (int *), безвредно: вы приводите один тип указателя к другому.

Преобразование функции, которая возвращает целое число вместо указателя, скорее всего, неверно. Компилятор пометил бы это, если бы вы явно не приводили его.

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

int *temp = (int *)malloc(sizeof(double));

Могут быть случаи, когда вы хотите сделать это, но я подозреваю, что они редки.

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

Другие вопросы по тегам