Я использую результат malloc?
В этом вопросе кто-то предложил в комментарии, что я не должен бросать результат malloc
т.е.
int *sieve = malloc(sizeof(int) * length);
скорее, чем:
int *sieve = (int *) malloc(sizeof(int) * length);
Почему это так?
31 ответ
Нет; Вы не разыгрываете результат, так как:
- Это ненужно, так как
void *
автоматически и безопасно повышается до любого другого типа указателя в этом случае. - Это добавляет беспорядок в код, приведения не очень легко читаются (особенно если тип указателя длинный).
- Это заставляет вас повторяться, что, как правило, плохо.
- Это может скрыть ошибку, если вы забыли включить
<stdlib.h>
, Это может вызывать сбои (или, что еще хуже, не приводить к сбою, пока в какой-то совершенно другой части кода). Подумайте, что произойдет, если указатели и целые числа имеют разные размеры; тогда вы скрываете предупреждение путем приведения и можете потерять биты вашего возвращенного адреса. Примечание: начиная с C11 неявные функции ушли из 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
,
В C вам не нужно приводить возвращаемое значение malloc
, Указатель на void, возвращаемый malloc
автоматически преобразуется в правильный тип. Однако, если вы хотите, чтобы ваш код компилировался с помощью компилятора C++, необходимо преобразование. Предпочтительной альтернативой среди сообщества является использование следующего:
int *sieve = malloc(sizeof *sieve * length);
что дополнительно освобождает вас от необходимости беспокоиться об изменении правой части выражения, если вы когда-либо измените тип sieve
,
Слепки плохие, как указали люди. Специально указатель отливок.
Вы делаете бросок, потому что:
- Это делает ваш код более переносимым между C и C++, и, как показывает опыт SO, многие программисты утверждают, что пишут на C, когда они действительно пишут на C++ (или C плюс локальные расширения компилятора).
- Несоблюдение этого требования может скрыть ошибку: обратите внимание на все ТАК примеры путаницы, когда писать
type *
противtype **
, - Идея, что он не замечает, что вы не смогли
#include
соответствующий заголовочный файл пропускает лес за деревьями. Это то же самое, что сказать: "Не беспокойтесь о том, что вы не попросили компилятор жаловаться на то, что не видите прототипы - этот pesky stdlib.h - ДЕЙСТВИТЕЛЬНО важная вещь, которую нужно помнить!" - Это вызывает дополнительную когнитивную перекрестную проверку. Он помещает (предполагаемый) желаемый тип прямо рядом с арифметикой, которую вы делаете для необработанного размера этой переменной. Могу поспорить, что вы могли бы сделать так исследование, которое показывает, что
malloc()
ошибки ловятся намного быстрее, когда есть актеры. Как и в случае с утверждениями, аннотации, раскрывающие намерения, уменьшают количество ошибок. - Повторять себя таким образом, чтобы машина могла это проверить, часто является отличной идеей. Фактически, это то, что является утверждением, и это использование приведения является утверждением. Утверждения по-прежнему являются наиболее общей техникой, которую мы используем для получения правильного кода, так как Тьюринг придумал эту идею много лет назад.
Как уже говорилось, это не нужно для 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++.
Из Википедии
Преимущества литья
Включение преобразования может позволить программе или функции на C компилироваться как C++.
Приведение допускает версии malloc до 1989 года, которые изначально возвращали символ *.
Приведение может помочь разработчику выявить несоответствия в размерах типов при изменении типа указателя назначения, особенно если указатель объявлен вдали от вызова 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
с кастингом.
В C вы можете неявно конвертировать указатель void в любой другой тип указателя, поэтому приведение не требуется. Использование одного может подсказать случайному наблюдателю, что есть какая-то причина, по которой он нужен, что может ввести в заблуждение.
Вы не приводите результат 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 лет?
В C вы получаете неявное преобразование из void*
на любой другой указатель (данных).
Приведение значения, возвращаемого malloc()
сейчас не нужно, но я хотел бы добавить один момент, который, кажется, никто не указал:
В древние времена, то есть до того, как ANSI C void *
как общий тип указателей, char *
это тип для такого использования. В этом случае приведение может отключить предупреждения компилятора.
Ссылка: C FAQ
Просто добавляя мой опыт, изучая компьютерную инженерию, я вижу, что два или три профессора, которых я видел, пишу на C, всегда бросают malloc, однако тот, кого я спросил (с огромным резюме и пониманием C), сказал мне, что это абсолютно ненужно, но Раньше он был абсолютно конкретным и привел студентов к менталитету быть абсолютно конкретным. По сути, приведение не изменит ничего в том, как оно работает, оно выполняет именно то, что говорит, выделяет память, и приведение не влияет на это, вы получаете ту же память, и даже если вы приведете ее к чему-то другому по ошибке (и каким-то образом уклонитесь от компилятора ошибки) C будет обращаться к нему таким же образом.
Изменить: Кастинг имеет определенную точку. Когда вы используете нотацию массива, сгенерированный код должен знать, сколько мест в памяти нужно продвинуть, чтобы достичь начала следующего элемента, это достигается с помощью приведения. Таким образом, вы знаете, что для двойника вы идете на 8 байтов вперед, а для целого - 4 и так далее. Таким образом, это не имеет никакого эффекта, если вы используете нотацию указателя, в нотации массива это становится необходимым.
Не обязательно ставить результаты malloc
, так как он возвращает void*
и void*
можно указать на любой тип данных.
Вот что говорит Справочное руководство по библиотеке GNU C:
Вы можете сохранить результат
malloc
в любую переменную указателя без приведения, потому что ISO C автоматически преобразует типvoid *
к другому типу указателя, когда это необходимо. Но приведение необходимо в контекстах, отличных от операторов присваивания или если вы хотите, чтобы ваш код выполнялся в традиционном C.
И действительно, стандарт ISO C11 (p347) гласит:
Указатель, возвращаемый в случае успешного выделения, выравнивается соответствующим образом, чтобы его можно было присвоить указателю на объект любого типа с фундаментальным требованием выравнивания, а затем использовать для доступа к такому объекту или массиву таких объектов в выделенном пространстве (до тех пор, пока пространство явно освобождено)
Указатель void является универсальным указателем, и C поддерживает неявное преобразование из типа указателя void в другие типы, поэтому нет необходимости явно указывать его типизацию.
Однако если вы хотите, чтобы один и тот же код работал идеально совместимым на платформе C++, которая не поддерживает неявное преобразование, вам необходимо выполнить приведение типов, так что все зависит от удобства использования.
Это зависит от языка программирования и компилятора. Если вы используете malloc
в C нет необходимости вводить cast, так как он будет автоматически набирать cast, однако если вы используете C++, вам следует набрать cast, потому что malloc
вернет void*
тип.
Возвращаемый тип void*, который может быть приведен к желаемому типу указателя данных, чтобы быть разыменованным.
В языке 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++) {
...
}
Этот вопрос является предметом злоупотреблений на основе мнения.
Иногда замечаю такие комментарии:
или
по вопросам, где OP использует кастинг. В самих комментариях есть гиперссылка на этот вопрос.
Это в любом случае неуместно и неверно. Нет правильного и неправильного, когда это действительно вопрос собственного стиля программирования.
Почему это происходит?
Это основано на двух причинах:
Этот вопрос действительно основан на мнении. Технически вопрос должен был быть закрыт как основанный на мнении много лет назад. На вопрос "Должен ли я" или "Не надо" или эквивалентный "Должен ли я" или "Не следует ли мне" просто не ответить сфокусированным без собственного мнения. Одна из причин закрыть вопрос заключается в том, что он "может привести к ответам, основанным на мнении", как это хорошо показано здесь.
Многие ответы (в том числе наиболее очевидным и общепринятом ответа на unwind) либо полностью или почти полностью на основе мнения (к таинственному "загромождали", который будет добавлены в ваш код, если вы литье или повторять себя будет плохо) и шоу четкая и целенаправленная тенденция к отказу от гипса. Они спорят об избыточности приведения с одной стороны, но также, что еще хуже, отстаивают решение ошибки, вызванной ошибкой / отказом самого программирования, - не
#include <stdlib.h>
если кто-то хочет использоватьmalloc()
.
Я хочу дать истинное представление о некоторых обсуждаемых моментах, не придерживаясь моего личного мнения. Особо следует отметить несколько моментов:
На такой очень болезненный вопрос, чтобы прийти к собственному мнению, нужен ответ с нейтральными плюсами и минусами. Не только за и за минусы.
В этом ответе приведен хороший обзор плюсов и минусов:
/questions/21132002/ya-ispolzuyu-rezultat-malloc/21132036#21132036
(Я лично считаю это по этой причине лучшим ответом на данный момент.)
Одна из причин, которая чаще всего встречается в качестве причины пропуска приведения, заключается в том, что приведение может скрыть ошибку.
Если кто-то использует неявное объявленное
malloc()
что возвращаетсяint
(неявные функции ушли из стандарта с C99) иsizeof(int) != sizeof(int*)
, как показано в этом вопросеПочему этот код имеет segfault в 64-битной архитектуре, но нормально работает в 32-битной?
актерский состав скроет ошибку.
Хотя это правда, это показывает только половину истории, так как отсутствие актеров было бы лишь перспективным решением еще более серьезной ошибки, не считая
stdlib.h
когда используешьmalloc()
.Это никогда не будет серьезной проблемой, если вы,
Используйте компилятор, соответствующий C99 или выше (что рекомендуется и должно быть обязательным), и
Не так уж и много, чтобы забыть включить
stdlib.h
, когда вы хотите использоватьmalloc()
в вашем коде, что само по себе является огромной ошибкой.
Некоторые люди спорят о совместимости кода C с C++, поскольку приведение типов обязательно в C++.
Прежде всего, чтобы сказать в целом: компиляция кода C с помощью компилятора C++ не является хорошей практикой.
На самом деле C и C++ - это два совершенно разных языка с разной семантикой.
Но если вы действительно хотите / должны сделать код C совместимым с C++ и наоборот, используйте переключатели компилятора вместо любого преобразования.
Поскольку приведение с тенденцией объявляется избыточным или даже вредным, я хочу сосредоточить внимание на этих вопросах, которые дают веские причины, почему приведение может быть полезным или даже необходимым:
- Приведение может быть невыгодным, когда ваш код, соответственно, тип назначенного указателя (и, соответственно, тип приведения) изменяется, хотя в большинстве случаев это маловероятно. Тогда вам также нужно будет поддерживать / изменять все приведения, и если у вас есть несколько тысяч вызовов функций управления памятью в вашем коде, это действительно может подвести итоги и снизить эффективность обслуживания.
Резюме:
Дело в том, что приведение является избыточным согласно стандарту C (уже начиная с ANSI-C (C89/C90)), если назначенный указатель указывает на объект, требующий фундаментального выравнивания (который включает в себя большинство всех объектов).
Приведение не требуется, поскольку в этом случае указатель выравнивается автоматически:
"Порядок и непрерывность памяти, выделенной последовательными вызовами функций align_alloc, calloc, malloc и realloc, не определены. Указатель, возвращаемый, если выделение выполнено успешно, соответствующим образом выровнено, чтобы его можно было назначить указателю на любой тип объекта с фундаментальное требование выравнивания, а затем используется для доступа к такому объекту или массиву таких объектов в выделенном пространстве (до тех пор, пока пространство не будет явно освобождено) ".
Источник: C18, §7.22.3 / 1
" Фундаментальное выравнивание - это действительное выравнивание, меньшее или равное
_Alignof (max_align_t)
. Фундаментальные выравнивания должны поддерживаться реализацией для объектов всех сроков хранения. Требования к выравниванию следующих типов должны быть фундаментальными:- все атомарные, квалифицированные или неквалифицированные базовые типы;
- все атомарные, квалифицированные или неквалифицированные перечислимые типы;
- все атомарные, квалифицированные или неквалифицированные типы указателей;
- все типы массивов, чей тип элемента требует фундаментального выравнивания;57)
- все типы, указанные в разделе 7, как полные типы объектов;
- все типы структур или объединений, все элементы которых имеют типы с фундаментальными требованиями к выравниванию, и ни один из элементов которых не имеет спецификатора выравнивания, указывающего выравнивание, которое не является фундаментальным выравниванием.
- Как указано в 6.2.1, более позднее объявление может скрыть предыдущее объявление ".
Источник: C18, §6.2.8/2
Однако, если вы выделяете память для определяемого реализацией объекта с расширенными требованиями к выравниванию, приведение будет необходимо.
Расширенное выравнивание представлено в большей согласованности, чем
_Alignof (max_align_t)
. Это определяется реализацией, поддерживаются ли какие-либо расширенные выравнивания, и продолжительность хранения, в течение которой они поддерживаются. Тип, требующий расширенного выравнивания, является сверхвыровненным типом.58)Источник. C18, §6.2.8/3
Все остальное - вопрос конкретного варианта использования и вашего собственного мнения.
Пожалуйста, будьте осторожны в своем обучении.
Я рекомендую вам сначала внимательно прочитать все ответы, сделанные до сих пор (а также их комментарии, которые могут указывать на неудачу), а затем составить собственное мнение, если вы или не используете результатmalloc()
в конкретном случае.
Пожалуйста, обратите внимание:
На этот вопрос нет правильного и неправильного ответа. Это вопрос стиля, и вы сами решаете, какой путь выбрать (если, конечно, образование или работа не принуждают вас к этому). Помните об этом и не позволяйте обмануть вас.
Последнее замечание: я проголосовал за то, чтобы закрыть этот вопрос как основанный на мнении, что действительно необходимо в течение многих лет. Если у вас есть привилегия закрывать / открывать заново, я хотел бы предложить вам сделать это тоже.
Люди привыкли к GCC и Clang испорчены. Там не все так хорошо.
За эти годы я был в ужасе от потрясающе старых компиляторов, которые мне пришлось использовать. Часто компании и менеджеры применяют ультраконсервативный подход к изменению компиляторов и даже не проверяют, будет ли работать новый компилятор (с лучшим соответствием стандартам и оптимизацией кода) в их системе. Практическая реальность для работающих разработчиков заключается в том, что когда вы пишете код, вам нужно охватить свои базы, и, к сожалению, приведение malloc - хорошая привычка, если вы не можете контролировать, какой компилятор может применяться к вашему коду.
Я также хотел бы предложить, чтобы многие организации применяли свой собственный стандарт кодирования, и это должно быть методом, которым люди следуют, если он определен. В отсутствие явных указаний я склоняюсь скорее к тому, чтобы компилировать везде, а не к рабской приверженности стандарту.
Аргумент, что в нынешних стандартах это не нужно, вполне обоснован. Но этот аргумент опускает практические аспекты реального мира. Мы пишем код не в мире, управляемом исключительно стандартами дня, а практиками того, что я люблю называть "полем реальности местного управления". И это изгибается и искривляется больше, чем когда-либо было пространство-время.:-)
YMMV.
Я склонен думать о том, чтобы использовать malloc как оборонительную операцию. Не красиво, не идеально, но в целом безопасно. (Честно говоря, если вы не включили stdlib.h, тогда у вас гораздо больше проблем, чем при использовании malloc!).
Нет, вы не разыгрываете результат 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++ имеет целый набор различных форм кастинга.
Я вставил приведение просто, чтобы показать неодобрение уродливой дыры в системе типов, которая позволяет скомпилировать код, такой как следующий фрагмент, без диагностики, даже если никакие преобразования не используются для плохого преобразования:
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 со всеми включенными предупреждениями
-Wall
и исправить все ошибки и предупреждения - Убедитесь, что нет переменных, объявленных как
auto
- Затем скомпилируйте его с помощью компилятора C++ с
-Wall
а также-std=c++11
, Исправьте все ошибки и предупреждения. - Теперь снова скомпилируйте с использованием компилятора C. Ваша программа должна теперь скомпилироваться без предупреждения и содержать меньше ошибок.
Эта процедура позволяет вам использовать строгую проверку типов в C++, тем самым уменьшая количество ошибок. В частности, эта процедура заставляет вас включать stdlib.h
или вы получите
malloc
не был объявлен в этой области
а также заставляет вас разыграть результат malloc
или вы получите
неверное преобразование из
void*
вT*
или каков ваш целевой тип.
Единственные преимущества от написания на C вместо C++, которые я могу найти:
- C имеет хорошо определенный ABI
- C++ может генерировать больше кода [исключения, RTTI, шаблоны, полиморфизм времени выполнения ]
Обратите внимание, что вторые минусы в идеальном случае должны исчезнуть при использовании общего для C подмножества вместе со статической полиморфной функцией.
Для тех, кто считает строгие правила C++ неудобными, мы можем использовать функцию C++11 с выведенным типом
auto memblock=static_cast<T*>(malloc(n*sizeof(T))); //Mult may overflow...
Я предпочитаю сниматься, но не вручную. Мой любимый использует g_new
а также g_new0
макросы из glib. Если glib не используется, я бы добавил похожие макросы. Эти макросы уменьшают дублирование кода без ущерба для безопасности типов. Если вы ошиблись в типе, вы бы получили неявное приведение между не пустыми указателями, что вызвало бы предупреждение (ошибка в C++). Если вы забыли включить заголовок, который определяет g_new
а также g_new0
, вы получите ошибку. g_new
а также g_new0
оба принимают одни и те же аргументы, в отличие от malloc
это требует меньше аргументов, чем calloc
, Просто добавь 0
получить нулевую инициализированную память. Код может быть скомпилирован с помощью компилятора C++ без изменений.
Кастинг только для C++, а не для C. В случае, если вы используете компилятор C++, вам лучше сменить его на компилятор C.
Приведение malloc не нужно в C, но обязательно в C++.
Приведение не нужно в C из-за:
void *
автоматически и безопасно повышается до любого другого типа указателя в случае C.- Это может скрыть ошибку, если вы забыли включить
<stdlib.h>
, Это может вызвать сбои. - Если указатели и целые числа имеют разный размер, тогда вы скрываете предупреждение путем приведения и можете потерять биты вашего возвращенного адреса.
- Если тип указателя изменяется при его объявлении, может также потребоваться изменить все строки, где
malloc
называется и разыгрывается.
С другой стороны, приведение может увеличить переносимость вашей программы. то есть, он позволяет программе или функции на C компилироваться как C++.
Концепция указателя void заключается в том, что он может быть приведен к любому типу данных, поэтому malloc возвращает void. Также вы должны знать об автоматической настройке типов. Поэтому указывать указатель не обязательно, хотя вы должны это сделать. Это помогает содержать код в чистоте и помогает отладке
Указатель void является универсальным указателем, и C поддерживает неявное преобразование из типа указателя void в другие типы, поэтому нет необходимости явно указывать его типизацию.
Однако если вы хотите, чтобы один и тот же код работал идеально совместимым на платформе C++, которая не поддерживает неявное преобразование, вам необходимо выполнить приведение типов, так что все зависит от удобства использования.
Как уже говорилось, это не нужно для C, но для C++.
Включение преобразования может позволить программе или функции на C компилироваться как C++.
В C это не нужно, поскольку void * автоматически и безопасно переводится в любой другой тип указателя.
Но если вы приведете его, он может скрыть ошибку, если вы забыли включитьstdlib.h. Это может вызывать сбои (или, что еще хуже, не приводить к сбою, пока в какой-то совершенно другой части кода).
Поскольку stdlib.h содержит прототип для malloc найден. В отсутствие прототипа для malloc стандарт требует, чтобы компилятор C предполагал, что malloc возвращает int. Если нет приведения, выдается предупреждение, когда это целое число присваивается указателю; однако, с использованием приведения это предупреждение не выдается, скрывая ошибку.
Основная проблема с
malloc
заключается в том, чтобы получить нужный размер .
Память вернула форму
malloc()
is untyped , и он не получит волшебным образом эффективный тип из-за простого приведения.
Я предполагаю, что оба подхода хороши, и выбор должен зависеть от намерения программиста.
- Если выделяете память для типа , используйте приведение.
ptr = (T*)malloc(sizeof(T));
- Если вы выделяете память для данного указателя, не используйте приведение.
ptr = malloc(sizeof *ptr);
Объявление 1
Первый метод гарантирует правильный размер, выделяя память для данного типа, а затем приводя ее, чтобы убедиться, что она назначена правильному указателю. Если используется неверный тип, компилятор выдаст предупреждение/ошибку. Если тип изменен, то компилятор укажет места, где код нуждается в рефакторинге.
Более того, первый метод можно объединить в макрос, аналогичный
new
оператор в С++.
#define NEW(T) ((T*)malloc(sizeof(T)))
...
ptr = NEW(T);
Причем этот метод работает, если есть .
Объявление 2
Второй метод не заботится о типах, он обеспечивает правильный размер, беря его из типа указателя. Основным преимуществом этого метода является автоматическая настройка размера хранилища при изменении типа. Это может сэкономить некоторое время (или ошибки) при рефакторинге.
Недостатком является то, что метод не работает, если
ptr
является
void*
но это может быть воспринято как хорошая вещь. И что он не работает с C++, поэтому его не следует использовать во встроенных функциях в заголовках, которые будут использоваться программами на C++.
Лично я предпочитаю второй вариант.
Для меня вывод и вывод: кастинг malloc
в C совершенно НЕ требуется, но если вы все же бросите, это не повлияет malloc
как malloc
по-прежнему будет выделять вам запрошенное вами благословенное пространство памяти. Еще один вывод - причина или одна из причин, по которым люди выполняют кастинг, и это позволяет им компилировать одну и ту же программу на C или C++.
Могут быть и другие причины, но другие причины почти наверняка рано или поздно приведут к серьезным неприятностям.
Вы можете, но не должны выполнять приведение в C. Вы должны выполнить приведение, если этот код скомпилирован как C++.