Излишние приведения указателей в C

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

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

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

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

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

Can I just say, please don't cast the return value of malloc? It is not required and can hide errors.

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

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

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

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

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
16
0
2 419
12
Перейти к ответу Данный вопрос помечен как решенный

Ответы 12

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

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

Ответ принят как подходящий

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

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

:-) ха-ха .. вы нашли вопрос. Большой.

Nils Pipenbrinck 20.09.2008 21:18

Это единственная проблема, которую я могу себе представить. ;)

unexist 20.09.2008 21:20

Просто запустите свой компилятор с -pedantic-errors, чтобы всегда быть в безопасности. ;)

unexist 20.09.2008 23:17

@onebyone Я ничего не цитировал. Это потребовало бы, чтобы я использовал его как резервную копию для оператора, который я использую. Я связал это, чтобы показать конфликт в том же теле. В моем ответе ни в коем случае не цитируются рекомендации CERT.

freespace 21.09.2008 19:22

Поскольку мы говорим о sizeof, я бы сказал, что вы должны написать это как: p = malloc (n * sizeof * p); Это короче, и в нем не используются ненужные (на мой взгляд, сбивающие с толку и запутанные) скобки вокруг аргумента. Они нужны только для имен типов, а не для других объектов.

unwind 27.10.2008 14:56

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

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

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

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

И с того же сайта: securecoding.cert.org/confluence/pages/… :-) Конечно интересные аргументы с обеих сторон

freespace 20.09.2008 21:26

Аргумент, приведенный в этой статье после некоторого размышления, не очень удачный. Вы должны просто использовать p = malloc (sizeof (* p) * n); чтобы избежать проблемы все вместе. Теперь вам не нужно менять тип в трех местах: один раз в объявлении p, один раз в malloc, один раз в приведении.

freespace 20.09.2008 21:54

Угадайте, если вы спросите двух программистов, всегда найдется третье, более прагматичное мнение. ;)

unexist 20.09.2008 22:09

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

Какие? Но он пишет на C, он не может использовать «новый».

Mladen Janković 20.09.2008 21:23

В том-то и дело - он не должен быть написан для C и C++, он должен быть лучшим для C, а затем полностью изменен для C++.

Douglas Leeder 20.09.2008 21:31
не согласен There is no need to rewrite perfectly fine C-code just to link it with a c++ module. That just raises the risk of introducing new bugs and gives no added value.
Nils Pipenbrinck 20.09.2008 22:48

@ Нильс - Да, есть. Что, если ваше приложение переопределяет new и delete для использования специального диспетчера памяти? Связанный код C, который вызывает malloc и free, по-прежнему будет использовать встроенный распределитель кучи, который может работать медленнее и / или не выполнять те задачи, которые вам требуются от диспетчера памяти. Вы не сможете отслеживать управление памятью ваших компонентов C. Если возможно, вам следует переписать для C++ при переносе на C++. Иногда в этом нет необходимости, но иногда это совершенно необходимо.

Chris Lutz 10.02.2010 21:07

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

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

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

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

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

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

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

Вы можете исключить два из этих трех типов местоположений и по-прежнему быть в безопасности, если типы меняются: T *p; p = malloc(sizeof *p); Без приведения типов, без возможности несоответствия типа или размера, без ошибок.

Chris Lutz 10.02.2010 21:09

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

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

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

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

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

Если вы включите предупреждения компилятора, вы узнаете, что забыли включить stdlib.h, потому что получите предупреждение о неявном объявлении malloc (и любой другой функции, которую вы использовали из stdlib).

Steve Jessop 21.09.2008 15:42

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

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.

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

Steve Jessop 22.09.2008 05:22

Конечно, все еще остается спорным вопрос о том, приносит ли безопасность типов какую-либо пользу, поскольку в любом случае можно использовать malloc (sizeof (* p) * n) и не нужно заботиться о типе.

Steve Jessop 22.09.2008 05:23

Использование mallocs требует указания, что вам нужен массив int (например). Поэтому вы должны использовать sizeof и, возможно, в конце добавить приведение (в коде, совместимом с C / C++). У функции есть небольшое преимущество: 1: полная статическая безопасность, 2: скрытие некоторых внутренних элементов, которые не должны отображаться в коде.

paercebal 24.09.2008 21:11

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

paercebal 24.09.2008 21:15

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

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

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

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

Evan Teran 21.09.2008 06:01

Да, его можно разыграть, но если вы этого не сделаете, этого не будет!

jfm3 22.09.2008 16:42

Думаю, вы ошибаетесь. void * неявно преобразуется в соответствующий тип указателя при назначении (все мы это знаем). Если приведение типа void * к T * является законным и имеет смысл, то неявное преобразование должно дать тот же результат. Так что упомянутой вами "ошибки" не существует.

Evan Teran 22.09.2008 17:17

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

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

Chris Lutz 10.02.2010 21:04

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

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