В частности, что опасно в приведении результата malloc?

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

  1. C FAQ: Что не так с приведением возвращаемого значения malloc?
  2. SO: Должен ли я явно приводить возвращаемое значение malloc()?
  3. SO: ненужные указатели в C
  4. SO: Я использую результат malloc?

Как в C FAQ, так и во многих ответах на вышеупомянутые вопросы есть таинственная ошибка, malloc возвращаемое значение может скрыть; однако ни один из них не дает конкретного примера такой ошибки на практике. Теперь обратите внимание, что я сказал ошибку, а не предупреждение.

Теперь приведен следующий код:

#include <string.h>
#include <stdio.h>
// #include <stdlib.h>

int main(int argc, char** argv) {

    char * p = /*(char*)*/malloc(10);
    strcpy(p, "hello");
    printf("%s\n", p);

    return 0;
}

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

anon@anon:~/$ gcc -Wextra nostdlib_malloc.c -o nostdlib_malloc
nostdlib_malloc.c: In function ‘main’:
nostdlib_malloc.c:7: warning: incompatible implicit declaration of built-in function ‘malloc’
anon@anon:~/$ ./nostdlib_malloc 
hello

Так может кто-нибудь привести конкретный пример кода ошибки компиляции или времени выполнения, которая может произойти из-за приведения malloc возвращаемое значение, или это просто городская легенда?

Править Я столкнулся с двумя хорошо написанными аргументами по этому вопросу:

  1. In Favor of Casting: CERT Advisory: немедленно приводить результат вызова функции выделения памяти в указатель на выделенный тип
  2. Против кастинга (ошибка 404 по состоянию на 2012-02-14: используйте копию машины обратного хода Интернет-архива от 2010-01-27.{2016-03-18:"Страница не может быть сканирована или отображена из-за robots.txt."})

7 ответов

Решение

Вы не получите ошибку компилятора, но предупреждение компилятора. Как указывают источники, которые вы цитируете (особенно первый), вы можете получить непредсказуемую ошибку времени выполнения при использовании приведения без включенияstdlib.h,

Так что ошибка на вашей стороне не в актерском составе, а в забвении stdlib.h, Компиляторы могут предположить, что malloc функция, возвращающая int, следовательно, преобразование void* указатель фактически возвращается malloc в int а затем к вашему типу указателя из-за явного приведения. На некоторых платформах int и указатели могут занимать различное количество байтов, поэтому преобразования типов могут привести к повреждению данных.

К счастью, современные компиляторы выдают предупреждения, которые указывают на вашу фактическую ошибку. Увидеть gcc вывод, который вы предоставили: он предупреждает вас, что неявное объявление (int malloc(int)) несовместим со встроенным malloc, Так gcc кажется знает malloc даже без stdlib.h,

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

if (0 == my_var)

вместо

if (my_var == 0)

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

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

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

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

Имена типов принадлежат объявлениям. Насколько это возможно, имена типов должны быть ограничены объявлениями и только объявлениями.

С этой точки зрения этот фрагмент кода плох

int *p;
...
p = (int*) malloc(n * sizeof(int));

и это намного лучше

int *p;
...
p = malloc(n * sizeof *p);

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

Предполагается, что непрототипированные функции возвращают int,

Итак, вы применяете int на указатель. Если указатели шире, чем int На вашей платформе это очень рискованное поведение.

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

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

Если вы сделаете это при компиляции в 64-битном режиме, ваш возвращаемый указатель будет усечен до 32-битного.

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

главный()
{
   char * c = (char *)malloc(2);
   printf("%p", c);
}

Предположим, что возвращенный указатель кучи является чем-то большим, чем то, что представляется в int, скажем, 0xAB00000000.

Если malloc не создан прототипом для возврата указателя, возвращаемое значение int будет первоначально в некотором регистре со всеми значащими битами. Теперь компилятор говорит: "Хорошо, как мне преобразовать и int в указатель". Это будет либо расширение знака, либо нулевое расширение 32-разрядных младших разрядов, которые, как было сказано, malloc "возвращает", пропустив прототип. Поскольку int подписано, я думаю, что преобразование будет расширением знака, которое в этом случае преобразует значение в ноль. С возвращаемым значением 0xABF0000000 вы получите ненулевой указатель, который также вызовет некоторое удовольствие, когда вы попытаетесь разыменовать его.

Правило многократного использования программного обеспечения:

В случае написания встроенной функции, в которой используется malloc(), для того, чтобы сделать ее повторно используемой и для кода C++, выполните явное приведение типов (например, (char*)); в противном случае компилятор будет жаловаться.

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

Раньше функция malloc() часто требовала преобразования. Для возвращаемого типа из malloc это указатель на void, а не на конкретный тип, например, массив char* или строка. А иногда компилятор не мог знать, как преобразовать этот тип.

      int size = 10; 
char* pWord = (char*)malloc(size); 

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

Невозможно разместить объект одного типа с другим объектом другого типа. Если объекты не являются классами, производными от общего корневого класса. И не всегда это возможно, бывают разные исключения. Поэтому в этом случае может потребоваться преобразование приведения.

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

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

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

      free((void*)pWord); 

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

С уважением, Адриан Бринас

Другие вопросы по тегам