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

2149

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

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

а не:

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

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

  • 207
    Кроме того, удобнее написать sieve = malloc (sizeof * sieve * length);
  • 24
    stackoverflow.com/q/7545365/168175
Показать ещё 6 комментариев
Теги:
casting
malloc

28 ответов

2003
Лучший ответ

Нет; вы не произносите результат, так как:

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

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

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

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

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

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


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

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

Так как сначала удерживая sizeof, в этом случае, умножение выполняется, по крайней мере, с помощью size_t math.

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

  • 100
    @unwind Нет, даже в строгом C-коде я использую приведение для переносимости. То, что я имею в виду под переносимостью, переносимо для более старых версий стандарта c, где void * не продвигается. Я цитирую «Язык программирования C» от K & R: «В C правильный метод состоит в том, чтобы объявить, что malloc возвращает указатель на void, а затем явно привести указатель к нужному типу с помощью приведения». Причина, по которой я навязываю такое требование, заключается в том, что у вас не всегда есть выбор, какой компилятор вы можете использовать.
  • 131
    @ chacham15 Хотя были более ранние версии языка C , где бросок был необходим (K & R C не имеют void* тип!), эти версии никогда не были стандартными. C89 (первая стандартизированная версия) требовала, чтобы malloc возвращал void* и чтобы void* был неявно конвертируемым.
Показать ещё 37 комментариев
322

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

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

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

Касты плохие, как указывали люди. Специально накладывается указатель.

  • 53
    @MAKZ Я бы сказал, что malloc(length * sizeof *sieve) делает его похожим на sizeof поэтому я думаю, что malloc(length * sizeof(*sieve)) более читабелен.
  • 16
    И malloc(length * (sizeof *sieve)) еще более читабельна. ПО МОЕМУ МНЕНИЮ.
Показать ещё 6 комментариев
321

Вы делаем, потому что:

  • Это делает ваш код более переносимым между C и С++, и, как показывает опыт SO, многие программисты утверждают, что они пишут на C, когда они действительно пишут на С++ (или локальный компилятор C плюс расширения).
  • Неспособность сделать это может скрыть ошибку: обратите внимание на все SO-примеры запутывания при написании type * versus type **.
  • Идея, что он не позволяет вам заметить, что вам не удалось #include соответствующий заголовочный файл пропускает лес для деревьев. Это то же самое, что сказать: "Не беспокойтесь о том, что вы не попросили компилятора жаловаться на то, что не видите прототипов, - что надоедливый stdlib.h - это важная вещь, которую нужно помнить!"
  • Он заставляет дополнительную когнитивную перекрестную проверку. Он помещает (предполагаемый) желаемый тип рядом с арифметикой, которую вы делаете для необработанного размера этой переменной. Бьюсь об заклад, вы можете сделать исследование SO, которое показывает, что ошибки malloc() пойманы намного быстрее, когда есть бросок. Как и в случае с утверждениями, аннотации, которые показывают намерение, уменьшают количество ошибок.
  • Повторение себя таким образом, что машина может проверять, часто является идеей большой. На самом деле, что такое утверждение, и это использование актеров - это утверждение. Утверждения по-прежнему являются наиболее общей техникой, которую мы имеем для правильного кода, поскольку Тьюринг много лет назад придумал эту идею.
  • 35
    @ulidtko Если вы не знали, можно написать код, который компилируется как в C, так и в C ++. На самом деле большинство заголовочных файлов такие, и они часто содержат код (макросы и встроенные функции). Наличие файла .c / .cpp для компиляции, так как оба не очень полезны, но один из случаев - это добавление поддержки C ++ throw при компиляции с помощью компилятора C ++ (но return -1; при компиляции с помощью компилятора C или чего-то еще).
  • 36
    Если бы у кого-то были встроенные вызовы malloc в заголовке, я бы не был впечатлен, #ifdef __cplusplus и extern "C" {} предназначены для этой работы, не добавляя дополнительные приведения.
Показать ещё 19 комментариев
155

Как и указано, он не нужен для 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 и С++.

  • 17
    Так как вы в любом случае используете макрос, почему бы вам не использовать new в определении C ++?
  • 58
    Потому что нет причин для этого. Это в основном для программ на C, которые компилируются с помощью компилятора C ++. Если вы собираетесь использовать «новый», единственное, что вы получите, это проблемы. Вам нужен также макрос бесплатно. И вам нужен макрос для освобождения массива, который не существует в C.
Показать ещё 13 комментариев
102

Из Wikipedia

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

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

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

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

Недостатки при литье

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

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

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

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

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

  • 1
    Что значит «приведение может помочь разработчику выявить несоответствия в определении размера, если тип указателя назначения изменится, особенно если указатель объявлен вдали от вызова malloc() »? Не могли бы вы привести пример?
  • 2
    @CoolGuy: см. Предыдущий комментарий к другому ответу . Но обратите внимание, что идиома p = malloc(sizeof(*p) * count) автоматически регистрирует изменения в типе, поэтому вам не нужно получать предупреждения и что-либо менять. Так что это не реальное преимущество по сравнению с лучшей альтернативой для не-каста.
Показать ещё 2 комментария
92

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

91

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

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

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

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

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

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

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

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

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

  • 6
    «Бесполезный беспорядок» - пренебрежительная гипербола, которая имеет тенденцию разрушать любую возможность убедить любого, кто уже не согласен с вами. Актерство определенно не бессмысленно; Ответы Рона Бурка и Каза приводят аргументы в пользу кастинга, с которым я очень согласен. Весят ли вопросы больше, чем те, о которых вы упоминаете, - это разумный вопрос. Для меня ваши проблемы выглядят относительно незначительными по сравнению с их.
  • 0
    «Пустой указатель может быть преобразован в / из любого другого типа указателя без явного приведения» не поддерживается 6.3.2.3. Возможно, вы думаете о «указателе на любой тип объекта»? «указатель на пустоту» и «указатель на функцию» не так легко конвертируемы.
Показать ещё 3 комментария
89

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

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

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

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

Ссылка: C Часто задаваемые вопросы

  • 2
    Отключение предупреждений компилятора - плохая идея.
  • 7
    @AlbertvanderHorst Нет, если вы делаете это, решая точную проблему, предупреждение должно вас предупредить.
Показать ещё 1 комментарий
48

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

47

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

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

  • 3
    За исключением уже упомянутого, приведение может скрыть ошибки и усложнить анализ кода для компилятора или статического анализатора.
  • 2
    «По сути, кастинг ничего не изменит в том, как он работает». Приведение к соответствующему типу не должно ничего менять, но если тип var изменится и приведение не будет соответствовать, могут ли возникнуть проблемы? IWO, тип cast и var должен синхронизироваться - вдвое больше работ по обслуживанию.
Показать ещё 1 комментарий
28

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

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

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

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

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

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

27

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

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

  • 1
    Это не обычный вариант использования для компиляции одного источника как C, так и C ++ (в отличие, скажем, от использования файла заголовка, содержащего объявления для связывания кода C и C ++ вместе). Использование malloc и друзей в C ++ является хорошим предупреждением о том, что оно заслуживает особого внимания (или переписывания в C).
  • 0
    «Пустой указатель является универсальным указателем» -> «Пустой указатель является универсальным указателем объекта ». Размеры указателей на функции могут превышать void * , поэтому void * недостаточно для правильного хранения указателя функции.
24

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

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

В языке 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++) {
   ...
}
  • 0
    «Указатель void может быть назначен любому указателю объекта ». Еще одной проблемой являются указатели на функции, хотя и не malloc() .
  • 0
    Назначение void* для / из указателя функции может привести к потере информации, поэтому «указатель void может быть назначен любому указателю» - проблема в этих случаях. Присвоение void* из malloc() любому указателю объекта не является проблемой.
Показать ещё 1 комментарий
14

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

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

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

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

YMMV.

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

  • 0
    Хорошо изложенный альтернативный взгляд, но что означает YMMV ? ( Ваш пробег может варьироваться - это самое распространенное определение, которое я мог найти, но не понимаю, как оно применимо здесь :)
11

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

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

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

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

По вопросу о литье и совместимости с С++, если вы пишете свой код, чтобы он компилировался как C и С++ (в этом случае вам нужно указать возвращаемое значение malloc при присвоении ему чего-то другого, кроме void *), вы можете сделать очень полезную вещь для себя: вы можете использовать макросы для кастинга, которые переводится в приведение стилей С++ при компиляции как С++, но сводятся к 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 вашей базы кода для этих идентификаторов покажет вам, где все ваши броски, поэтому вы можете проверить, не являются ли какие-либо из них неправильными.

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

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

     -Wold-style-cast (C++ and Objective-C++ only)
         Warn if an old-style (C-style) cast to a non-void type is used
         within a C++ program.  The new-style casts (dynamic_cast,
         static_cast, reinterpret_cast, and const_cast) are less vulnerable
         to unintended effects and much easier to search for.

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

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

  • 0
    Как уже отмечалось, я бы порекомендовал не смешивать код C и C ++. Однако, если у вас есть веская причина для этого, макросы могут быть полезны.
  • 0
    @ Phil1970 Все это написано на одном связном диалекте, который переносится на компиляторы C и C ++ и использует преимущества некоторых возможностей C ++. Все должно быть скомпилировано как C ++, иначе все должно быть скомпилировано как C.
Показать ещё 1 комментарий
10

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

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

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

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

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

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

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

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;

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

TL;DR

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


Боковые заметки:

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

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

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

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

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

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

10

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

10

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

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

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

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

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

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

или какой бы тип вашего целевого объекта не был.

Единственные преимущества написания в C вместо С++, которые я могу найти, - это

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

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

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

auto memblock=static_cast<T*>(malloc(n*sizeof(T))); //Mult may overflow...
  • 17
    Используйте 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 или наоборот, в зависимости от зависимостей проекта). Теперь не должно быть причин лишать себя каких-либо приятных черт любого языка ...
Показать ещё 3 комментария
9

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

  • 10
    « Это не обязательно - хотя вы должны это делать » - я думаю, что здесь есть противоречие!
  • 5
    Я думаю, что вы должны прочитать этот пост кому-то, и посмотреть, понимают ли они, что вы пытаетесь сказать. Затем перепишите его, пояснив, что вы хотите сказать. Я действительно не могу понять, каков твой ответ.
5
  • Как и указано, он не нужен для C, но для С++.

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

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

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

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

5

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

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

4

Здесь актерский состав не нужен вообще. Под языком C вы можете неявно конвертировать пустой указатель в любой другой указатель.

2

Кастинг malloc не нужен в C, но обязателен в C++.

Кастинг не нужен в C из-за:

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

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

  • 0
    @ Aashish, в C ++ вы вообще не делаете никаких выделений памяти. Даже не используя новый. ps: я тоже не понижаюсь.
-3

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

Таким образом, cast возвращаемый указатель из malloc. В следующем коде компилятор может быть уверен, что сите фактически присваивается точка целому числу.

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

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

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню