Вопрос

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

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

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

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

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

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

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

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

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

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

Это было полезно?

Решение

Мне кажется уместным опубликовать ответ, поскольку я оставил комментарий: 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 ++, поэтому на него есть как минимум два ответа, ИМХО:

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.

Одна из возможных ошибок, которую это может привести, заключается в том, что вы компилируете в 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 ++.Например, если вы ориентируетесь на машинную архитектуру с различными типами указателей, вы можете столкнуться с очень сложной ошибкой, если не добавите приведение.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top