Нужны некоторые разъяснения относительно литья в C - PullRequest
2 голосов
/ 21 декабря 2009

Я только что читал о плохой практике приведения возвращаемого значения malloc. Если я правильно понял, то абсолютно законно оставлять актерский состав, как это делается неявно (и его следует оставить из-за других проблем, которые он может вызвать). Ну, мой вопрос, когда я должен затем бросить свои ценности? Есть какое-то общее правило или что-то? Например, этот код компилируется без ошибок с gcc -W -Wall (за исключением неиспользованного bar, но это не главное):

float foo(void) {
    double bar = 4.2;
    return bar;
}

int main(void) {
    double bar = foo();
    return 0;
}

Я сейчас в замешательстве. Каковы хорошие практики и правила о кастинге?

Спасибо.

Ответы [ 13 ]

9 голосов
/ 21 декабря 2009

Есть несколько ситуаций, которые требуют совершенно правильного литья в C. Остерегайтесь широких утверждений, таких как «литье - это всегда плохой дизайн», так как они явно и явно поддельные.

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

unsigned i = ...;
unsigned long s = (unsigned long) i * i;

, чтобы избежать переполнения. Или в

double d = (double) i / 5;

, чтобы заставить компилятор переключаться на деление с плавающей точкой. Или в

s = (unsigned) d * 3 + i;

, чтобы взять всю часть значения с плавающей запятой. И так далее (примеры бесконечны).

Другая группа допустимых применений - это идиомы, то есть хорошо зарекомендовавшие себя методы кодирования. Например, классическая идиома C, когда функция принимает в качестве входных данных константный указатель и возвращает неконстантный указатель на те же (потенциально постоянные) данные, как, например, стандартный strstr. Реализация этой идиомы обычно требует использования приведения, чтобы отбросить постоянство входных данных. Кто-то может назвать это плохим дизайном, но в действительности нет лучшей альтернативы дизайна в C. В противном случае, это не будет устоявшейся идиомой:)

Также стоит упомянуть, например, что педантично правильное использование стандартной функции printf может потребовать приведения аргументов в общем случае. (Как и спецификатор формата %p, ожидающий указатель void * в качестве аргумента, это означает, что аргумент int * должен быть так или иначе преобразован в void *. Явное приведение является наиболее логичным способом выполнения преобразование.).

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

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

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

8 голосов
/ 21 декабря 2009

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

Кстати, предупреждение gcc для этого - -Wconversion. К сожалению -Wall и -Wextra все еще оставляют много хороших предупреждений.

Вот флаги, которые я использую, когда я хочу, чтобы gcc был очень похож на lint

-pedantic -std=c99 -ggdb3 -O0 -Wall -Wextra -Wformat=2 -Wmissing-include-dirs -Winit-self -Wswitch-default -Wswitch-enum -Wunused-parameter -Wfloat-equal -Wundef -Wshadow -Wlarger-than-1000 -Wunsafe-loop-optimizations -Wbad-function-cast -Wcast-qual -Wcast-align -Wconversion -Wlogical-op -Waggregate-return -Wstrict-prototypes -Wold-style-definition -Wmissing-prototypes -Wmissing-declarations -Wpacked -Wpadded -Wredundant-decls -Wnested-externs -Wunreachable-code -Winline -Winvalid-pch -Wvolatile-register-var -Wstrict-aliasing=2 -Wstrict-overflow=2 -Wtraditional-conversion -Wwrite-strings

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

4 голосов
/ 21 декабря 2009

Вы не можете спрашивать о приведении в 'C' без понимания того, что приведение охватывает более одного типа операций. Существуют два, преобразование типов и приведение типов. В C ++, поскольку он имеет больше информации о типах, он создает 4 типа приведений и кодифицирует это с эксклюзивной нотацией. reinterpret_cast<>, const_cast<>, dynamic_cast<> и static_cast<>. У вас их нет в C, поскольку у всех приведений есть синтаксис (ctype), но причины для них остаются, и это помогает понять, почему приведение требуется, даже если ваш вопрос касался именно C.

«Потребность» в статическом приведении - это то, что вы показываете в своем примере. Компилятор сделает их за вас, даже если вы не укажете его - однако, уровень предупреждения поднимется достаточно высоко, и компилятор предупредит вас, если произойдет потеря точности при переходе от двойного к плавающему (ваш return bar; заявление). Добавление приведения говорит компилятору, что потеря точности была запланирована.

Вторым наименее опасным составом является постоянный состав <>. Используется для удаления const или volatile из типа. Это обычно происходит, когда структуры имеют внутренние «кэши». Таким образом, вызывающая сторона может иметь константную версию вашей структуры, но «внутренней функции» необходимо обновить кэш, поэтому ей придется преобразовывать указатель на константную структуру в обычную структуру для обновления внутреннего поля.

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

    char **ptostr = (char **p) "this is not a good idea"; 

Вероятно, правильным решением было использование '&', и именно так кастинг получает плохую репутацию. Такие приведения могут быть использованы для добра или зла. Я использовал его в ответе на другой вопрос о том, как найти наименьшую мощность 2 , чтобы использовать мощность FPU в процессоре. Лучший пример хорошего использования - это реализация связанных списков. Если ссылки находятся в самих объектах, вы должны привести от указателя ссылки обратно к вложенному объекту (хороший вариант для макроса offsetof, если ссылки не могут быть в верхней части структуры).

Динамическое приведение не имеет языковой поддержки в C, но обстоятельство все еще возникает. Если у вас есть гетерогенный список, вы можете проверить, что объект был заданного типа, используя поле в заголовке ссылки списка. Реализованный вручную, вы проверите тип как совместимый и вернете NULL, если это не так. Это специальная версия реинтерпретации.

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

4 голосов
/ 21 декабря 2009

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

4 голосов
/ 21 декабря 2009

Мое простое указание - если ему нужен актерский состав, возможно, это неправильно. Если вам не нужны актеры, не используйте их.

2 голосов
/ 21 декабря 2009

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

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

1 голос
/ 21 декабря 2009

На что вы обращаете внимание, это неявное преобразование типов. Это считается безопасным, если вы начинаете с типа, имеющего более ограниченный диапазон, чем тот, которым вы заканчиваете, то есть, если короткое значение int, то все в порядке, равно как float to double.

Я весьма удивлен, что gcc не генерирует предупреждение при преобразовании двойного числа в число с плавающей точкой; Я верю, что компилятор Microsoft делает.

1 голос
/ 21 декабря 2009

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

1 голос
/ 21 декабря 2009
double bar = foo();

То, что здесь происходит, называется рекламным преобразованием , где значение приведенной переменной зарезервировано после преобразования. Обратное неверно, то есть float -> double. Единственный ответ - разыгрывать только тогда, когда вам действительно нужно. Кастинг - признак плохого дизайна.

0 голосов
/ 22 декабря 2009

Существует как минимум два вида приведений:

  • Приведение числовых значений, которые вызывают изменение представления . (Это термин искусства, который вы найдете в Harbison и очень полезном справочнике Стила C .) Эти броски в основном безобидны; единственный способ причинить вред - это, например, преобразовать более широкий тип в более узкий, и в этом случае вы намеренно выбрасываете биты. Только вы, программист, знаете, безопасно ли выбрасывать эти биты.

    C также имеет коварную особенность, заключающуюся в том, что он может выполнять изменение представления от вашего имени, когда вы присваиваете, возвращаете или передаете числовое выражение, тип которого точно не соответствует типу ассоциированного lvalue, result или аргумента. Я думаю, что эта функция пагубна, но она прочно встроена в C-образ действий. Опция gcc -Wconversion сообщит вам, где компилятор изменяет представление от вашего имени, без запроса.

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

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


P.S. Я думал, что можно было бы написать короткий, полезный ответ на этот вопрос. Неправильно!

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