В Си, почему некоторые люди приводят указатель перед освобождением?

167

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

free((float *)velocity);
free((float *)acceleration);
free((char *)label);

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

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

РЕДАКТИРОВАТЬ: Этот вопрос не является дубликатом другого вопроса. Другой вопрос является частным случаем этого вопроса, который, я думаю, очевиден, если близкие избиратели прочитают все ответы.

Колофон: Я ставлю галочку "const answer", потому что это реальная причина, почему это может быть необходимо сделать; тем не менее, ответ о том, что это был пре-ANSI C обычай (по крайней мере, среди некоторых программистов), кажется, является причиной его использования в моем случае. Много хороших моментов многими людьми здесь. Спасибо за ваш вклад.

Доктор Персона Человек II
источник
13
«Какой смысл напоминать себе о типе данных прямо перед его освобождением?» Может быть, узнать, сколько памяти будет освобождено?
m0skit0
12
@Codor Компилятор не выполняет освобождение, а операционная система.
m0skit0
20
@ m0skit0 "Может быть, знать, сколько памяти будет освобождено?" Типа не нужно знать, сколько нужно освободить. В ролях по этой причине только плохое кодирование.
user694733
9
@ m0skit0 Приведение ради читабельности всегда является плохим кодированием, потому что приведение изменяет интерпретацию типов и может скрывать серьезные ошибки. Когда читаемость необходима, комментарии лучше.
user694733
66
В древние времена, когда динозавры ходили по земле и писали книги по программированию, я полагаю, что не было void*предстандартного C, а только char*. Поэтому, если ваши археологические находки выявили код, приводящий параметр к free (), я считаю, что он должен быть либо из этого периода, либо написан существом того времени. Хотя я не могу найти источник для этого, поэтому я воздержусь от ответа.
Лундин

Ответы:

171

Приведение может потребоваться для разрешения предупреждений компилятора, если указатели есть const. Вот пример кода, который вызывает предупреждение без приведения аргумента free:

const float* velocity = malloc(2*sizeof(float));
free(velocity);

И компилятор (gcc 4.8.3) говорит:

main.c: In function main’:
main.c:9:5: warning: passing argument 1 of free discards const qualifier from pointer target type [enabled by default]
     free(velocity);
     ^
In file included from main.c:2:0:
/usr/include/stdlib.h:482:13: note: expected void *’ but argument is of type const float *’
 extern void free (void *__ptr) __THROW;

Если вы используете free((float*) velocity);компилятор, перестает жаловаться.

Манос Николаидис
источник
2
@ m0skit0, который не объясняет, почему кто-то бросил float*перед освобождением. Я пробовал free((void *)velocity);с gcc 4.8.3. Конечно, это не сработает с древним компилятором
Manos Nikolaidis
54
Но зачем вам динамически выделять постоянную память? Вы никогда не сможете использовать это!
Nils_M
33
@ Nils_M это упрощенный пример, чтобы подчеркнуть. В реальном коде функции я выделил неконстантную память, назначил значения, привел к константному указателю и возвратил его. Теперь есть указатель на заранее назначенную константную память, которую кто-то должен освободить.
Манос Николаидис
2
Пример : «Эти подпрограммы возвращают строку во вновь загруженной памяти malloc, на которую указывает * stringValueP, которую вы должны в конце концов освободить. Иногда объявляется, что функция ОС, которую вы используете для освобождения памяти, принимает в качестве аргумента указатель на что-то непостоянное, потому что * stringValueP является указателем на const ».
Карстен С
3
Ошибочное, если функция принимает в const char *pкачестве аргумента , а затем освобождает его, правильная вещь , чтобы сделать , это не актерский , pчтобы char*перед вызовом бесплатно. Во const char *p-первых, это не означает, что он *pдолжен быть принят , поскольку он изменяется и должен быть объявлен соответствующим образом. (И если вместо указателя на const требуется указатель const, int *const pвам не нужно приводить, так как он на самом деле допустим и, следовательно, отлично работает без приведений.)
Ray
59

У предстандарта C не было ничего, void*кроме char*, так что вы должны были привести все переданные параметры Если вы столкнетесь с древним кодом C, вы можете найти такие приведения.

Подобный вопрос со ссылками .

Когда первый стандарт C был выпущен, прототипы таНос и бесплатно изменилось от того , char*на void*что у них еще есть сегодня.

И, конечно, в стандарте C такие броски излишни и просто вредят читабельности.

Лундин
источник
23
Но почему вы freeприводите аргумент к тому же типу, который уже есть?
jwodder
4
@chux Проблема с предварительным стандартом заключается в том, что никаких обязательств ни на что не существует. Люди просто указывали на книгу K & R для канона, потому что это было единственное, что у них было. И как мы можем видеть из нескольких примеров в K & R 2nd edition, сами K & R не понимают, как приведение параметра будет freeработать в стандартном C (вам не нужно приводить). Я не читал 1-е издание, поэтому не могу сказать, были ли они запутаны и в нестандартные времена 80-х годов.
Лундин
7
Предварительный стандарт C не имел void*, но у него также не было прототипов функций, поэтому приведение аргумента по- freeпрежнему было ненужным даже в K & R (при условии, что все типы указателей данных использовали одно и то же представление).
Ян Эбботт
6
По нескольким причинам, указанным в комментариях, я не думаю, что этот ответ имеет смысл.
R .. GitHub ОСТАНОВИТЬ ЛЬДА
4
Я не вижу, как этот ответ действительно ответил бы на что-нибудь важное. Первоначальный вопрос предполагает приведение к другим типам, а не только char *. Какой смысл в старых компиляторах void? Чего добились бы такие броски?
августа
34

Вот пример, где free потерпит неудачу без приведения:

volatile int* p = (volatile int*)malloc(5 * sizeof(int));
free(p);        // fail: warning C4090: 'function' : different 'volatile' qualifiers
free((int*)p);  // success :)
free((void*)p); // success :)

В C вы можете получить предупреждение (есть в VS2012). В C ++ вы получите ошибку.

За исключением редких случаев, кастинг просто раздувает код ...

Изменить: я бросил, чтобы void*не int*показывать сбой. Он будет работать так же, как int*будет преобразован в void*неявно. Добавлен int*код.

egur
источник
Обратите внимание, что в коде, опубликованном в вопросе, приведения выполняются не к void *, а к float *и char *. Эти броски не просто посторонние, они ошибаются.
Эндрю Хенле
1
Вопрос на самом деле об обратном.
m0skit0
1
Я не понимаю ответ; в каком смысле free(p)потерпит неудачу? Это даст ошибку компилятора?
Кодор
1
Это хорошие моменты. То же самое и с constуказателями квалификаторов, очевидно.
Лундин
2
volatileсуществует с тех пор, как C был стандартизирован, если не дольше. Это не было добавлено в C99.
R .. GitHub ОСТАНОВИТЬ ЛЬДА
30

Старая причина: 1. При использовании в free((sometype*) ptr)коде явно указывается тип, указатель должен рассматриваться как часть free()вызова. Явное приведение полезно, когда free()его заменяют на (сделай сам) DIY_free().

#define free(ptr) DIY_free(ptr, sizeof (*ptr))

DIY_free()Был (есть) способ, особенно в режиме отладки, чтобы сделать во время выполнения анализа указателя освобождения. Это часто сочетается с DIY_malloc()добавлением предложений, подсчета общего использования памяти и т. Д. Моя группа использовала эту технику в течение многих лет, прежде чем появились более современные инструменты. Он обязал, чтобы предмет, являющийся свободным, был приведен к типу, который был первоначально выделен.

  1. Учитывая много часов, потраченных на отслеживание проблем с памятью и т. Д., Маленькие хитрости, такие как приведение типа free'd, помогут в поиске и сужении отладки.

Современный: избегать constи volatileпредупреждать в соответствии с указаниями Manos Nikolaidis @ и @egur . Думал , что я хотел бы отметить эффекты 3 классификаторов : const, volatile, и restrict.

[править] Добавлено char * restrict *rp2за @R .. комментарий

void free_test(const char *cp, volatile char *vp, char * restrict rp, 
    char * restrict *rp2) {
  free(cp);  // warning
  free(vp);  // warning
  free(rp);  // OK
  free(rp2);  // warning
}

int main(void) {
  free_test(0,0,0,0);
  return 0;
}
Chux - Восстановить Монику
источник
3
restrictэто не проблема, потому что он расположен - он влияет на объект, а rpне на указатель типа. Если бы вы имели char *restrict *rp, то это было бы важно.
R .. GitHub ОСТАНОВИТЬ ЛЬДА
16

Вот еще одна альтернативная гипотеза.

Нам говорят, что программа была написана до C89, что означает, что она не может обойти какое-то несоответствие с прототипом free, потому что не только не было такой вещи, как constи void *до C89, не было такой вещи, как прототип функции до C89. stdlib.hСам был выдумкой комитета. Если бы системные заголовки не удосужились объявить freeвообще, они бы сделали это так:

extern free();  /* no `void` return type either! */

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

Тем не менее, это еще не означает, что необходимо было приводить аргументы в freeбольшинстве компиляторов K & R. Функция как

free_stuff(a, b, c)
    float *a;
    char *b;
    int *c;
{
    free(a);
    free(b);
    free(c);
}

должен был быть правильно скомпилирован. Поэтому я думаю, что у нас есть программа, написанная для работы с ошибочным компилятором для необычной среды: например, для среды, где sizeof(float *) > sizeof(int)компилятор не будет использовать соответствующее соглашение о вызовах для указателей, если вы их в точку вызова.

Я не знаю ни о какой такой среде, но это не значит, что ее не было. Наиболее вероятные кандидаты, которые приходят на ум, - это урезанные «крошечные C» компиляторы для 8- и 16-битных микро в начале 1980-х. Я также не удивлюсь, узнав, что у ранних Crays были такие проблемы.

zwol
источник
1
Первая половина я полностью согласен с. А вторая половина - интригующая и правдоподобная гипотеза.
chux - Восстановить Монику