Я использую результат malloc? - PullRequest
2250 голосов
/ 03 марта 2009

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

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

вместо:

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

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

Ответы [ 26 ]

48 голосов
/ 28 марта 2014

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

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

30 голосов
/ 09 октября 2015

Это то, что Справочник по библиотеке GNU C говорит:

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

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

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

29 голосов
/ 13 июля 2014

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

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

28 голосов
/ 26 апреля 2013

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

26 голосов
/ 04 сентября 2015

В языке 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++) {
   ...
}
25 голосов
/ 17 марта 2014

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

15 голосов
/ 04 декабря 2015

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

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

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

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

YMMV.

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

12 голосов
/ 12 июня 2015

Лучшее, что можно сделать при программировании на 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 подмножества вместе с static polymorphic Feature.

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

auto memblock=static_cast<T*>(malloc(n*sizeof(T))); //Mult may overflow...
12 голосов
/ 30 марта 2016

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

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

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

11 голосов
/ 16 августа 2017

Нет, вы не разыгрываете результат 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 ++ имеет целый набор различных форм кастинга.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...