Я использую результат malloc?


2409

В этом вопросе кто-то предложил в комментарии, что я не должен приводить результат malloc, т.е.

int *sieve = malloc(sizeof(int) * length);

скорее, чем:

int *sieve = (int *) malloc(sizeof(int) * length);

Почему это так?


222
Кроме того, удобнее написать sieve = malloc (sizeof * sieve * length);
Уильям Перселл


3
Ответы здесь - минное поле односторонности. Ответ "это зависит". Это не обязательно, чтобы программа работала. Однако некоторые стандарты кодирования требуют этого ... Например, см. Стандарт кодирования CERT C
Dr. Person Person II,

Странно, все согласны с тем, что вы не бросаете NULL. (возможно, именно поэтому C ++ и представил nullptr: C ++ не допускает никаких неявных приведений указателей)
Sapphire_Brick

Вы можете, но вам не нужно. Но вам нужно в C ++.
user12211554

Ответы:


2218

Нет ; Вы не разыгрываете результат, так как:

  • В этом нет необходимости, так как void *в этом случае он автоматически и безопасно переводится на любой другой тип указателя.
  • Это добавляет беспорядок в код, приведения не очень легко читаются (особенно если тип указателя длинный).
  • Это заставляет вас повторяться, что, как правило, плохо.
  • Это может скрыть ошибку, если вы забыли включить <stdlib.h>. Это может вызывать сбои (или, что еще хуже, не приводить к сбою, пока в какой-то совершенно другой части кода). Подумайте, что произойдет, если указатели и целые числа имеют разные размеры; тогда вы скрываете предупреждение путем приведения и можете потерять биты вашего возвращенного адреса. Примечание: начиная с C99 неявные функции ушли из C, и эта точка больше не актуальна, так как нет автоматического предположения о возврате необъявленных функций int.

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

Также отметьте, как отмечают комментаторы, что вышесказанное говорит о прямом C, а не C ++. Я очень твердо верю в C и C ++ как отдельные языки.

Чтобы добавить дальше, ваш код без необходимости повторяет информацию о типе ( int), которая может вызвать ошибки. Лучше отменить ссылку на указатель, используемый для хранения возвращаемого значения, чтобы «заблокировать» их вместе:

int *sieve = malloc(length * sizeof *sieve);

Это также перемещает lengthвперед для увеличения видимости и удаляет лишние скобки с sizeof; они нужны только тогда, когда аргумент является именем типа. Многие люди, кажется, не знают (или игнорируют) это, что делает их код более многословным. Помните: sizeofэто не функция! :)


Хотя перемещение lengthв переднюю часть может улучшить видимость в некоторых редких случаях, следует также обратить внимание, что в общем случае лучше написать выражение в виде:

int *sieve = malloc(sizeof *sieve * length);

Так как сохранение sizeofпервого, в этом случае гарантирует, что умножение выполняется по крайней мере с size_tматематикой.

Для сравнения: по malloc(sizeof *sieve * length * width)сравнению с malloc(length * width * sizeof *sieve)второй может переполнить , length * widthкогда widthи lengthболее мелкие виды , чем size_t.


22
Пожалуйста, рассмотрите возможность обновления ответа. Приведение больше не опасно, и повторять себя не обязательно плохо (избыточность может помочь отловить ошибки).
нет. местоимения м.

12
Компиляторы изменились. Современный компилятор предупредит вас о пропущенном объявлении malloc.
нет. местоимения м.

55
@nm Хорошо. Я думаю, что неправильно предполагать, что у любого, кто читает здесь, есть определенный компилятор. Кроме того, поскольку C11 полностью отказался от концепции «неявной функции», я этого не знал. Тем не менее, я не вижу смысла в добавлении бессмысленного состава. Вы также делаете, int x = (int) 12;чтобы прояснить ситуацию?
расслабиться

22
@nm, если явное приведение пустого указателя «помогло» решить ошибку, вы, скорее всего, столкнулись с неопределенным поведением, которое означало бы, что в рассматриваемой программе, вероятно, имеется гораздо худшая, необнаруженная ошибка, с которой вы еще не сталкивались. И однажды, холодным зимним вечером, вы вернетесь домой с работы и обнаружите, что ваша страница GitHub заполнена сообщениями о проблемах с жалобами на демонов, вылетающих из носов пользователей
Брэдэн Бест

12
@unwind Даже я согласен с вами, (int)12это не сравнимо. 12 являетсяint , бросок не делает просто ничего. Возвращает значение malloc()is void *, а не тип указателя. (Если это не так void *. Поэтому аналогия (int)12будет тем, (void*)malloc(…)что никто не обсуждает.)
Амин Негм-Авад

376

В C вам не нужно приводить возвращаемое значение malloc. Указатель на void, возвращаемый mallocфункцией, автоматически преобразуется в правильный тип. Однако, если вы хотите, чтобы ваш код компилировался с помощью компилятора C ++, необходимо преобразование. Предпочтительной альтернативой среди сообщества является использование следующего:

int *sieve = malloc(sizeof *sieve * length);

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

Слепки плохие, как указали люди. Особенно указатель бросает.


71
@ MAKZ Я бы сказал, что malloc(length * sizeof *sieve)это выглядит как sizeofпеременная, так что я думаю, что malloc(length * sizeof(*sieve))она более читабельна.
Майкл Андерсон

21
И malloc(length * (sizeof *sieve))еще более читабельным. ИМХО.
Тоби Спейт

19
@ Майкл Андерсон, ()кроме вопроса, обратите внимание, что предложенный вами стиль изменил порядок. Рассмотрим, когда количество элементов вычисляется примерно так length*width, сохраняя sizeofпервое в этом случае, обеспечим, чтобы умножение было выполнено хотя бы с size_tматематикой. Сравните malloc(sizeof( *ptr) * length * width)с malloc(length * width * sizeof (*ptr))- 2-й может переполнить, length*widthкогда width,lengthменьшие типы, что size_t.
chux - Восстановить Монику

3
@ chux, это не очевидно, но ответ был отредактирован так, чтобы мой комментарий был менее уместным - первоначальное предложение былоmalloc(sizeof *sieve * length)
Майкл Андерсон

12
C не является C ++. Притворение, что они есть, в конечном итоге приведет к растерянности и грусти. Если вы используете C ++, то приведение в стиле C также плохо (если вы не используете очень старый компилятор C ++). И static_cast>()(или reinterpret_cast<>()) не совместим ни с одним диалектом C.
Дэвид С.

349

Вы делаете бросок, потому что:

  • Это делает ваш код более переносимым между C и C ++, и, как показывает опыт SO, многие программисты утверждают, что они пишут на C, когда они действительно пишут на C ++ (или C плюс локальные расширения компилятора).
  • Несоблюдение этого требования может скрыть ошибку : обратите внимание на все SO примеры путаницы, когда писать type *против type **.
  • Идея, что он не позволит вам заметить, что вам не удалось #includeнайти подходящий заголовочный файл, пропускает лес для деревьев . Это то же самое, что сказать: «Не беспокойтесь о том, что вы не попросили компилятор жаловаться на то, что не видите прототипы - этот pesky stdlib.h - ДЕЙСТВИТЕЛЬНО важная вещь, которую нужно помнить!»
  • Это вызывает дополнительную когнитивную перекрестную проверку . Он помещает (предполагаемый) желаемый тип прямо рядом с арифметикой, которую вы делаете для необработанного размера этой переменной. Могу поспорить, что вы могли бы сделать ТАК исследование, которое показывает, что malloc()ошибки обнаруживаются намного быстрее, когда есть бросок. Как и с утверждениями, аннотации, раскрывающие намерения, уменьшают количество ошибок.
  • Повторять себя так, как может проверить машина, - это часто хорошая идея. Фактически, это и есть утверждение, и это использование приведения является утверждением. Утверждения по-прежнему являются наиболее общей техникой, которую мы используем для получения правильного кода, так как Тьюринг придумал эту идею много лет назад.

39
@ulidtko Если вы не знали, можно написать код, который компилируется как в C, так и в C ++. На самом деле большинство заголовочных файлов такие, и они часто содержат код (макросы и встроенные функции). Наличие .c/ .cppfile для компиляции, так как оба не очень полезны, но одним из случаев является добавление throwподдержки C ++ при компиляции с помощью компилятора C ++ (но return -1;при компиляции с помощью компилятора C или любого другого).
Hyde

37
Если бы у кого-то были встроенные вызовы malloc в заголовке, я бы не был впечатлен, #ifdef __cplusplus и extern "C" {} предназначены для этой работы, не добавляя дополнительные приведения.
Пол

15
Ну, точка 1 не имеет значения, так как C! = C ++, остальные точки также тривиальны, если вы используете переменную в своем mallocвызове: char **foo = malloc(3*sizeof(*foo));если она полностью доказана: 3 указателя на указатели на символы. затем цикл и делай foo[i] = calloc(101, sizeof(*(foo[i])));. Выделите массив из 101 символа, аккуратно инициализированный нулями. Не нужен актерский состав. в этом случае измените объявление unsigned charили любой другой тип, и вы все еще в порядке
Элиас Ван Отегем

34
Когда я понял, что получил, вот оно! Фантастический ответ. Впервые здесь, в StackOverflow, мне +1 два противоположных ответа! +1 Нет, вы не разыгрываете, и +1 Да, вы разыгрываете! ЛОЛ. Вы, ребята, потрясающие. И я и мои ученики решили: я снимаюсь. Тип ошибок, которые делают ученики, легче заметить при касте.
Доктор Беко

15
@Leushenko: Повторять себя таким образом, который не может быть подтвержден ни машиной, ни местной инспекцией, плохо. Повторять себя способами, которые могут быть подтверждены такими средствами, менее вредно. Учитывая это struct Zebra *p; ... p=malloc(sizeof struct Zebra);, malloc не может избежать дублирования информации о типе p, но ни компилятор, ни проверка локального кода не обнаружат никаких проблем, если один тип изменился, а другой - нет. Измените код на, p=(struct Zebra*)malloc(sizeof struct Zebra);и компилятор закричит, если тип приведения не совпадает p, и локальная проверка покажет ...
суперкат

170

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

#ifdef __cplusplus
# define NEW(type, count) ((type *)calloc(count, sizeof(type)))
#else
# define NEW(type, count) (calloc(count, sizeof(type)))
#endif

Таким образом, вы все еще можете написать это очень компактно:

int *sieve = NEW(int, 1);

и он скомпилируется для C и C ++.


17
Так как вы в любом случае используете макрос, почему бы вам не использовать newв определении C ++?
Хосам Али

63
Потому что нет причин для этого. Это в основном для программ на C, которые компилируются с помощью компилятора C ++. Если вы собираетесь использовать «новый», единственное, что вы получите, это проблемы. Вам нужен также макрос бесплатно. И вам нужен макрос для освобождения массива, который не существует в C.
quinmars

8
Не говоря уже о том, что освобождает память не вы, а, возможно, используемая вами библиотека C и т. Д. Множество возможных проблем без какой-либо выгоды.
Квинмарс

86
@Hosam: Да, это определенно так. Если вы используете, newвы должны использовать, deleteи если вы используете malloc()вы должны free(). Никогда не смешивайте их.
Грэм Перроу

17
Если кто-то использует этот подход, вызов макроса NEW, вероятно, является плохой идеей, поскольку ресурс никогда не возвращается с использованием delete(или DELETE), поэтому вы смешиваете свой словарный запас. Вместо этого, назвав его MALLOC, или, скорее, CALLOCв этом случае, имело бы больше смысла.
март

139

Из Википедии :

Преимущества литья

  • Включение преобразования может позволить программе или функции на C компилироваться как C ++.

  • Приведение допускает версии malloc до 1989 года, которые изначально возвращали char *.

  • Приведение может помочь разработчику выявить несоответствия в определении размера при изменении типа указателя назначения, особенно если указатель объявлен вдали от вызова malloc () (хотя современные компиляторы и статические анализаторы могут предупреждать о таком поведении, не требуя приведения).

Недостатки кастинга

  • В соответствии со стандартом ANSI C приведение является избыточным.

  • Добавление приведения может скрыть неудачу при включении заголовка stdlib.h, в котором найден прототип для malloc. В отсутствие прототипа для malloc стандарт требует, чтобы компилятор C предполагал, что malloc возвращает int. Если нет приведения, выдается предупреждение, когда это целое число присваивается указателю; однако, с использованием приведения это предупреждение не выдается, скрывая ошибку. В некоторых архитектурах и моделях данных (таких как LP64 в 64-разрядных системах, где long и указатели являются 64-разрядными, а int - 32-разрядными), эта ошибка может фактически привести к неопределенному поведению, поскольку неявно объявленный malloc возвращает 32- битовое значение, тогда как фактически определенная функция возвращает 64-битное значение. В зависимости от соглашений о вызовах и расположения памяти это может привести к разрушению стека. Эта проблема с меньшей вероятностью останется незамеченной в современных компиляторах, поскольку они единообразно выдают предупреждения о том, что была использована необъявленная функция, предупреждение все равно будет появляться. Например, стандартное поведение GCC заключается в отображении предупреждения, которое гласит «несовместимое неявное объявление встроенной функции» независимо от того, присутствует ли приведение или нет.

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

Хотя malloc без преобразования является предпочтительным методом, и большинство опытных программистов выбирают его , вы должны использовать все, что захотите, зная о проблемах.

То есть: если вам нужно скомпилировать программу на C как C ++ (хотя это отдельный язык), вы должны привести результат использования malloc.


1
Что значит «приведение может помочь разработчику выявить несоответствия в определении размера, если тип указателя назначения изменится, особенно если указатель объявлен вдали от malloc()вызова »? Не могли бы вы привести пример?
Спикатрикс

3
@CoolGuy: см. Предыдущий комментарий к другому ответу . Но обратите внимание, что p = malloc(sizeof(*p) * count)идиома автоматически регистрирует изменения в типе, поэтому вам не нужно получать предупреждения и что-либо менять. Так что это не реальное преимущество по сравнению с лучшей альтернативой для не-каста.
Питер Кордес

7
Это правильный ответ: есть плюсы и минусы, и все сводится к вкусу (если код не должен компилироваться как C ++ - тогда приведение является обязательным).
Питер - Восстановить Монику

3
Пункт 3 является спорным, так как если тип указателя изменяется при его объявлении, следует проверить каждый экземпляр таНоса, перераспределить и бесплатно inolving этого типа. Кастинг заставит вас сделать именно это.
Микаэль Рой

104

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


100

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

Наиболее распространенная причина, по которой люди приводят результат malloc, заключается в том, что они не уверены в том, как работает язык Си. Это предупреждающий знак: если вы не знаете, как работает тот или иной языковой механизм, не стоит угадывать. Посмотрите это или спросите о переполнении стека.

Некоторые комментарии:

  • Пустой указатель может быть преобразован в / из любого другого типа указателя без явного преобразования (C11 6.3.2.3 и 6.5.16.1).

  • Однако C ++ не допускает неявное приведение между void*другим типом указателя. Так что в C ++ приведение было бы правильным. Но если вы программируете на C ++, вы должны использовать, newа не malloc (). И вы никогда не должны компилировать код C, используя компилятор C ++.

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

  • Если компилятор C не может найти функцию, потому что вы забыли включить заголовок, вы получите сообщение об ошибке компилятора / компоновщика. Так что, если вы забыли включить <stdlib.h>это не важно, вы не сможете создать свою программу.

  • На древних компиляторах, которые следуют версии стандарта, которой более 25 лет, забывание о включении <stdlib.h>может привести к опасному поведению. Потому что в этом древнем стандарте функции без видимого прототипа неявно преобразовывали возвращаемый тип в int. Явное приведение результата из malloc могло бы скрыть эту ошибку.

    Но это действительно не проблема. Вы не используете компьютер 25 лет, так зачем вам использовать компилятор 25 лет?


9
«Бесполезный беспорядок» - пренебрежительная гипербола, которая имеет тенденцию разрушать любую возможность убедить любого, кто уже не согласен с вами. Актерство определенно не бессмысленно; Ответы Рона Бурка и Каз дают аргументы в пользу кастинга, с которым я очень согласен. Весят ли вопросы больше, чем те, о которых вы упоминаете, - это разумный вопрос. Для меня ваши проблемы выглядят относительно незначительными по сравнению с их.
Дон Хэтч

«Пустой указатель может быть преобразован в / из любого другого типа указателя без явного приведения» не поддерживается 6.3.2.3. Возможно, вы думаете о «указателе на любой тип объекта»? «Указатель на пустоту» и «Указатель на функцию» не так легко конвертируемы.
chux - Восстановить Монику

Действительно, ссылка была неполной. Соответствующей частью «неявности» является правило простого присваивания 6.5.16.1. «один операнд - указатель на тип объекта, а другой - указатель на квалифицированную или неквалифицированную версию void». Я добавил эту ссылку в ответ для полноты.
Лундин

91

В C вы получаете неявное преобразование из void *любого другого указателя (данных).


6
@Jens: ОК, может быть, более правильная формулировка - «неявное преобразование». Как использование интегральной переменной в выражении с плавающей точкой.
EFraim

@EFraim Это на самом деле приведет к приведению, и неявному.
Безумный физик

71

Приведение значения, возвращаемого malloc() теперь не является необходимым, но я хотел бы добавить один момент, который, кажется, никто не указал:

В древние времена, т. Е. До того, как ANSI C предоставил void *общий тип указателей, char *это тип для такого использования. В этом случае приведение может отключить предупреждения компилятора.

Ссылка: C FAQ


2
Отключение предупреждений компилятора - плохая идея.
Альберт ван дер Хорст

8
@AlbertvanderHorst Нет, если вы делаете это, решая точную проблему, предупреждение должно вас предупредить.
Дэн Бешард

@ Дан. Если под решением точной задачи подразумевается перезапись подпрограммы для возврата современных типов ANSI C вместо char *, я согласен. Я бы не назвал это закрытием компилятора. Не поддавайтесь менеджерам, которые настаивают на отсутствии предупреждений компилятора, вместо того, чтобы использовать их при каждой перекомпиляции для поиска возможных проблем. Groetjes Альберт
Альберт ван дер Хорст

53

Просто добавив мой опыт, изучая компьютерную инженерию, я вижу, что два или три профессора, которых я видел, пишу на C, всегда бросают malloc, однако тот, кого я спросил (с огромным резюме и пониманием C), сказал мне, что это абсолютно ненужно, но Раньше он был абсолютно конкретным и привел студентов к менталитету быть абсолютно конкретным. По сути, приведение ничего не изменит в том, как оно работает, оно делает именно то, что говорит, выделяет память, и приведение не влияет на это, вы получаете ту же память, и даже если вы приведете ее к чему-то другому по ошибке (и каким-то образом уклонитесь от компилятора ошибки) C будет обращаться к нему таким же образом.

Изменить: кастинг имеет определенную точку. Когда вы используете нотацию массива, сгенерированный код должен знать, сколько мест в памяти нужно продвинуть, чтобы достичь начала следующего элемента, это достигается с помощью приведения. Таким образом, вы знаете, что для двойника вы идете на 8 байтов вперед, а для целого - 4 и так далее. Таким образом, это не имеет никакого эффекта, если вы используете нотацию указателя, в нотации массива это становится необходимым.


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

2
«По сути, кастинг ничего не изменит в том, как он работает». Приведение к соответствующему типу не должно ничего менять, но если тип var изменится и приведение не будет соответствовать, могут ли возникнуть проблемы? IWO, тип cast и var должен синхронизироваться - вдвое больше работ по обслуживанию.
chux - Восстановить Монику

Я понимаю, почему профессионалы предпочитают кастинг. Кастинг может быть полезен с образовательной точки зрения, когда он передает информацию инструктору, и код ученика не нуждается в поддержке - его код выброса. Тем не менее, с точки зрения кодирования, рецензирования и сопровождения , p = malloc(sizeof *p * n);все так просто и лучше.
chux - Восстановить Монику

53

Не обязательно приводить результаты malloc, так как он возвращает void*, и a void*может указывать на любой тип данных.


34

Указатель void является указателем общего объекта, и C поддерживает неявное преобразование из типа указателя void в другие типы, поэтому нет необходимости явно указывать его тип.

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


2
Это не обычный случай использования для компиляции одного источника как C, так и C ++ (в отличие, скажем, от использования файла заголовка, содержащего объявления для связывания кода C и C ++ вместе). Использование mallocand friends в C ++ является хорошим предупреждением о том, что оно заслуживает особого внимания (или переписывания в C).
Тоби Спейт

1
«Пустой указатель является универсальным указателем» -> «Пустой указатель является универсальным указателем объекта ». Размеры указателей на функции могут превышать void *, что void *недостаточно для правильного хранения указателя на функцию.
chux - Восстановить Монику

мое намерение этой линии было то же самое, но в любом случае спасибо @chux за предложение.
Endeavor

33

Вот что говорит Справочное руководство по библиотеке GNU C :

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

И действительно, стандарт ISO C11 (p347) гласит:

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


31

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


1
void* может быть приведен к нужному типу, но в этом нет необходимости, поскольку он будет автоматически преобразован. Таким образом, актерский состав не является необходимым и фактически нежелательным по причинам, указанным в ответах с высокой оценкой.
Тоби Спейт

но только если вам нужно разыменовать его «на лету», если вы создадите переменную вместо этого, она будет безопасно и автоматически преобразована в эффективный тип переменной без приведения (в C).
Феррарези

28

В языке C указатель void может быть назначен любому указателю, поэтому не следует использовать приведение типа. Если вы хотите «безопасное распределение типов», я могу порекомендовать следующие макро-функции, которые я всегда использую в своих проектах на C:

#include <stdlib.h>
#define NEW_ARRAY(ptr, n) (ptr) = malloc((n) * sizeof *(ptr))
#define NEW(ptr) NEW_ARRAY((ptr), 1)

С этим на месте вы можете просто сказать

NEW_ARRAY(sieve, length);

Для нединамических массивов третий обязательный макрос функции

#define LEN(arr) (sizeof (arr) / sizeof (arr)[0])

что делает циклы массива более безопасными и удобными:

int i, a[100];

for (i = 0; i < LEN(a); i++) {
   ...
}

«Пустой указатель может быть назначен любому указателю объекта ». Функциональные указатели - еще одна проблема, хотя и не malloc()единственная.
chux - Восстановить Монику

Присвоение void*указателю функции функции to / from может привести к потере информации, поэтому «указатель void может быть назначен любому указателю» - проблема в этих случаях. Присвоение void*, from malloc() любому указателю объекта не является проблемой.
chux - Восстановить Монику

doПетля комментарии , связанные с макросами , включающих цикл еще , что интересно , от заголовка вопроса. Удаление этого комментария. Снесу этот позже тоже.
chux - Восстановить Монику

27

Это зависит от языка программирования и компилятора. Если вы используете mallocв C, нет необходимости вводить cast, так как он автоматически набирает cast. Однако, если вы используете C ++, вы должны набрать cast, потому mallocчто вернет void*тип.


1
Функция malloc также возвращает пустой указатель на C, но правила языка отличаются от C ++.
Август Карлстром

16

Люди привыкли к GCC и Clang испорчены. Там не все так хорошо.

Я был довольно напуган за эти годы потрясающе старыми компиляторами, которые я должен был использовать. Часто компании и менеджеры применяют ультраконсервативный подход к изменению компиляторов и даже не тестируют будет ли работать новый компилятор (с лучшим соответствием стандартам и оптимизацией кода) в их системе. Практическая реальность для работающих разработчиков заключается в том, что когда вы пишете код, вам нужно охватить свои базы, и, к сожалению, приведение malloc - хорошая привычка, если вы не можете контролировать, какой компилятор может применяться к вашему коду.

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

Аргумент, что в нынешних стандартах это не нужно, вполне обоснован. Но этот аргумент опускает практические аспекты реального мира. Мы пишем код не в мире, управляемом исключительно стандартами дня, а практиками того, что я люблю называть «полем реальности местного управления». И это изгибается и искривляется больше, чем когда-либо было пространство-время. :-)

YMMV.

Я склонен думать о том, чтобы использовать malloc как оборонительную операцию. Не красиво, не идеально, но в целом безопасно. (Честно говоря, если вы не включены stdlib.h , то вы имеете пути больше проблем , чем литье таНоса!).


15

Я вставил приведение просто, чтобы показать неодобрение уродливой дыры в системе типов, которая позволяет скомпилировать код, такой как следующий фрагмент, без диагностики, даже если никакие преобразования не используются для плохого преобразования:

double d;
void *p = &d;
int *q = p;

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

На самом деле, хорошей практикой является использование malloc(и друзей) функций, которые возвращаются unsigned char *и в основном никогда не используются void *в вашем коде. Если вам нужен общий указатель на любой объект, используйте char *или unsigned char *, и приведите в обоих направлениях. Единственное расслабление, которое можно потворствовать, возможно, это использование таких функций, как memsetи memcpyбез приведения.

Что касается приведения типов и совместимости с C ++, если вы пишете свой код так, чтобы он компилировался как на C, так и на C ++ (в этом случае вы должны приводить возвращаемое значение mallocпри назначении ему чего-то другого, чем void *), вы можете сделать очень полезную вещь для себя: вы можете использовать макросы для приведения, которые преобразуются в приведения в стиле C ++ при компиляции в C ++, но уменьшаются до приведения в C при компиляции в C:

/* In a header somewhere */
#ifdef __cplusplus
#define strip_qual(TYPE, EXPR) (const_cast<TYPE>(EXPR))
#define convert(TYPE, EXPR) (static_cast<TYPE>(EXPR))
#define coerce(TYPE, EXPR) (reinterpret_cast<TYPE>(EXPR))
#else
#define strip_qual(TYPE, EXPR) ((TYPE) (EXPR))
#define convert(TYPE, EXPR) ((TYPE) (EXPR))
#define coerce(TYPE, EXPR) ((TYPE) (EXPR))
#endif

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

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

Чтобы помочь вам придерживаться этих макросов, у компилятора GNU C ++ (не C!) Есть прекрасная функция: дополнительная диагностика, которая создается для всех случаев приведения типов в стиле C.

     -Wold-style-cast (только C ++ и Objective-C ++)
         Предупреждать, если используется приведение старого типа (в стиле C) к не пустому типу
         в программе на C ++. Кастинги нового стиля (dynamic_cast,
         static_cast, reinterpret_cast и const_cast) менее уязвимы
         непреднамеренные эффекты и гораздо легче искать.

Если ваш код на C компилируется как C ++, вы можете использовать эту -Wold-style-castопцию, чтобы выяснить все вхождения (type)синтаксиса приведения, которые могут проникнуть в код, и проследить за этими диагностиками, заменив его подходящим выбором из перечисленных выше макросов (или комбинация, если необходимо).

Такая обработка преобразований является единственным крупнейшим техническим обоснованием для работы в «Чистом С»: комбинированный диалект С и С ++, который, в свою очередь, технически оправдывает приведение возвращаемого значения malloc.


Как уже отмечалось, я бы порекомендовал не смешивать код C и C ++. Однако, если у вас есть веская причина для этого, макросы могут быть полезны.
Phil1970

@ Phil1970 Все это написано на одном связном диалекте, который переносится на компиляторы C и C ++ и использует некоторые возможности C ++. Все должно быть скомпилировано как C ++, иначе все должно быть скомпилировано как C.
Kaz

То есть то, что я пытался сказать в предыдущем комментарии, это то, что нет никакого смешивания C и C ++. Намерение состоит в том, что весь код скомпилирован как C или все скомпилированы как C ++.
Каз

15

Лучшее, что можно сделать при программировании на C, когда это возможно:

  1. Сделайте так, чтобы ваша программа компилировалась через компилятор C со всеми включенными предупреждениями -Wallи исправляла все ошибки и предупреждения
  2. Убедитесь, что нет переменных, объявленных как auto
  3. Затем скомпилируйте его, используя компилятор C ++ с -Wallи -std=c++11. Исправьте все ошибки и предупреждения.
  4. Теперь снова скомпилируйте с использованием компилятора C. Ваша программа должна теперь скомпилироваться без предупреждения и содержать меньше ошибок.

Эта процедура позволяет вам использовать строгую проверку типов в C ++, тем самым уменьшая количество ошибок. В частности, эта процедура заставляет вас включить stdlib.hили вы получите

malloc не был объявлен в этой области

а также заставляет вас разыграть результат mallocили вы получите

неверное преобразование из void*вT*

или каков ваш целевой тип.

Единственная выгода от написания на C вместо C ++, которую я могу найти:

  1. C имеет хорошо определенный ABI
  2. C ++ может генерировать больше кода [исключения, RTTI, шаблоны, полиморфизм времени выполнения ]

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

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

auto memblock=static_cast<T*>(malloc(n*sizeof(T))); //Mult may overflow...

18
Используйте C-компилятор для C-кода. Используйте компилятор C ++ для кода C ++. Нет если, нет но. Переписать ваш C-код на C ++ - это совсем другое, и может - а может и не стоить - времени и рисков.
Тоби Спейт

2
Я хотел бы добавить к совету @TobySpeight: если вам нужно использовать код C в проекте C ++, вы обычно можете скомпилировать код C как C (например gcc -c c_code.c), код C ++ как C ++ (например g++ -c cpp_code.cpp), а затем связать их вместе (например, gcc c_code.o cpp_code.oили наоборот, в зависимости от зависимости проекта). Теперь не должно быть причин лишать себя каких-либо приятных черт любого языка ...
аутист

1
@ user877329 Это более разумная альтернатива кропотливому добавлению приведений к коду, которые уменьшают читаемость кода, только ради того, чтобы быть «C ++ совместимым».
аутист

1
Вероятно, главное преимущество в этом контексте заключается в том, что C позволяет писать p = malloc(sizeof(*p));, что не требует изменения в первую очередь, если pменяется имя другого типа. Предлагаемое «преимущество» приведения заключается в том, что вы получаете ошибку компиляции, если pнеправильный тип, но даже лучше, если он просто работает.
Питер Кордес

1
Я хотел бы отметить, что написание на C может быть необходимо, когда нацелено на платформы, не имеющие надлежащих C ++ компиляторов. Исключения и шаблоны - это функции, которые обычно помогают c ++ генерировать меньший и / или более эффективный код, в то время как полиморфизм времени выполнения в C ++ в основном эквивалентен C.
user7860670

15

Нет, вы не разыгрываете результат malloc().

В общем, вы не бросаете в или изvoid * .

Типичная причина, по которой они этого не делают, заключается в том, что неудача #include <stdlib.h>может остаться незамеченной. Это уже давно не проблема, поскольку C99 сделал неявные объявления функций недопустимыми, поэтому, если ваш компилятор соответствует хотя бы C99, вы получите диагностическое сообщение.

Но есть гораздо более веская причина не вводить ненужные приведения указателей:

В C приведение указателя почти всегда является ошибкой . Это из-за следующего правила ( §6.5 p7 в N1570, последний проект для C11):

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

Это также известно как строгое правило наложения имен . Таким образом, следующий код является неопределенным поведением :

long x = 5;
double *p = (double *)&x;
double y = *p;

И, иногда удивительно, следующее также:

struct foo { int x; };
struct bar { int x; int y; };
struct bar b = { 1, 2};
struct foo *p = (struct foo *)&b;
int z = p->x;

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

ТЛ; др

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


Примечания стороны:

  • Существуют случаи, когда вам действительно требуется приведение к void *, например, если вы хотите напечатать указатель:

    int x = 5;
    printf("%p\n", (void *)&x);

    Приведение здесь необходимо, потому что printf()это функция с переменным числом, поэтому неявные преобразования не работают.

  • В C ++ ситуация иная. Приведение типов указателей является довольно распространенным (и правильным) при работе с объектами производных классов. Следовательно, имеет смысл, что в C ++ преобразование в и из неvoid * является неявным. C ++ имеет целый набор различных форм кастинга.


1
В ваших примерах вы избегаете void *. есть различие между приведением типа double * к int * и наоборот. malloc возвращает указатель, выровненный по самому большому стандартному типу, так что не будет нарушено никаких правил псевдонимов, даже если кто-то приведёт этот выровненный указатель к другому типу.
P__J__

Псевдоним не имеет ничего общего с выравниванием и остальной частью вашего комментария - вы, очевидно, не поняли сути.

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

Проблема строгого псевдонима не имеет ничего общего с пустыми указателями. Чтобы получить ошибки, вызванные строгими нарушениями псевдонимов, вы должны прекратить обращение к указанным данным. И поскольку вы не можете отменить ссылку на указатель void, такие ошибки по определению связаны не с указателем void, а с чем-то другим.
Лундин

Скорее, вам нужно будет создать правило, запрещающее все приведения указателей. Но тогда как бы вы написали такие вещи, как процедуры сериализации и аппаратное программирование? Вещи, которые сила С Такие броски хороши, если вы знаете, что делаете.
Лундин

15

Я предпочитаю сниматься, но не вручную. Мой любимый использует g_newи g_new0макросы из glib. Если glib не используется, я бы добавил похожие макросы. Эти макросы уменьшают дублирование кода без ущерба для безопасности типов. Если вы ошиблись в типе, вы бы получили неявное приведение между не пустыми указателями, что вызвало бы предупреждение (ошибка в C ++). Если вы забудете включить заголовок, который определяет g_newи g_new0, вы получите ошибку. g_newи g_new0оба принимают одинаковые аргументы, в отличие от mallocэтого требует меньше аргументов, чем calloc. Просто добавьте, 0чтобы получить нулевую инициализированную память. Код может быть скомпилирован с помощью компилятора C ++ без изменений.


12

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


9

Концепция за указателем void заключается в том, что он может быть приведен к любому типу данных, поэтому malloc возвращает void. Также вы должны знать об автоматической настройке типов. Так что указывать указатель не обязательно, хотя вы должны это сделать. Это помогает содержать код в чистоте и помогает отладке


11
« Это не обязательно - хотя вы должны это делать » - я думаю, что здесь есть противоречие!
Тоби Спейт

5
Я думаю, что вы должны прочитать этот пост кому-то, и посмотреть, понимают ли они, что вы пытаетесь сказать. Затем перепишите его, пояснив, что вы хотите сказать. Я действительно не могу понять, каков твой ответ.
Билл Вуджер,

9

Указатель void является универсальным указателем, и C поддерживает неявное преобразование из типа указателя void в другие типы, поэтому нет необходимости явно указывать его типизацию.

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


9
  1. Как уже говорилось, это не нужно для C, но для C ++.

  2. Включение преобразования может позволить программе или функции на C компилироваться как C ++.

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

  4. Но если вы приведете его, он может скрыть ошибку, если вы забыли включить stdlib.h . Это может вызывать сбои (или, что еще хуже, не приводить к сбою, пока в какой-то совершенно другой части кода).

    Поскольку stdlib.h содержит прототип для malloc найден. В отсутствие прототипа для malloc стандарт требует, чтобы компилятор C предполагал, что malloc возвращает int. Если нет приведения, выдается предупреждение, когда это целое число присваивается указателю; однако, с использованием приведения это предупреждение не выдается, скрывая ошибку.


7

Приведение malloc не нужно в C, но обязательно в C ++.

Приведение не нужно в C из-за:

  • void * автоматически и безопасно повышается до любого другого типа указателя в случае C.
  • Это может скрыть ошибку, если вы забыли включить <stdlib.h>. Это может вызвать сбои.
  • Если указатели и целые числа имеют разный размер, тогда вы скрываете предупреждение путем приведения и можете потерять биты вашего возвращенного адреса.
  • Если тип указателя изменяется при его объявлении, может также потребоваться изменить все строки, где mallocвызывается и приводится.

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


0

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

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


0

Вы можете, но не обязательно приводить в C. Вы должны приводить, если этот код скомпилирован как C ++.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.