Запрещено ли #define в отраслевых стандартах?

Я первый студент-информатик, и мой профессор сказал, что #define запрещен в отраслевых стандартах вместе с #if, #ifdef, #else и несколькими другими директивами препроцессора. Он использовал слово "запрещено" из-за неожиданного поведения.

Это точно? Если да, то почему?

Существуют ли, по сути, какие-либо стандарты, запрещающие использование этих директив?

Ответ 1

Сначала я слышал об этом.

Нет; #define и т.д. широко используются. Иногда слишком широко используется, но определенно используется. Есть места, где стандарт C предусматривает использование макросов - вы не можете легко их избежать. Например, §7.5 Ошибки <errno.h> говорит:

Макросы

     EDOM
     EILSEQ
     ERANGE

которые расширяются до целочисленных константных выражений с типом int, различными положительными значениями и которые подходят для использования в директивах препроцессора #if;...

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

В одном стандарте вы можете проверить стандарт MISRA C (2012); который имеет тенденцию запрещать вещи, но даже это признает, что иногда требуются #define и др. (раздел 8.20, правила с 20.1 по 20.14 охватывают препроцессор С).

NASA GSFC (Центр космических полетов Годдарда) C Стандарты кодирования просто говорят:

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

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

CERT C Coding Standard содержит ряд рекомендаций относительно использования препроцессора и подразумевает, что вы должны минимизировать использование препроцессор, но не запрещает его использование.

Stroustrup хотел бы сделать препроцессор неуместным в С++, но этого еще не произошло. Как Peter примечания, некоторые стандарты С++, такие как Стандарты кодирования JSF AV С++ (Joint Strike Fighter, Air Vehicle) с 2005 года диктуют минимальное использование препроцессора C. По сути, правила JSF AV С++ ограничивают его тагами #include и танцем #ifndef XYZ_H/#define XYZ_H/.../#endif, который предотвращает множественные включения одного заголовка. У С++ есть некоторые опции, которые недоступны в C - в частности, лучше поддерживать типизированные константы, которые затем могут использоваться в местах, где C не позволяет их использовать. См. Также static const vs #define vs enum для обсуждения проблем там.

Рекомендуется минимизировать использование препроцессора - его часто злоупотребляют, по крайней мере, столько, сколько он используется (см. Boost preprocessor 'library' для иллюстрации того, как далеко вы можете перейти с препроцессором C).

Резюме

Препроцессор является неотъемлемой частью C и #define и #if и т.д. не может быть полностью устранен. Утверждение профессора в этом вопросе не является общепринятым: #define запрещен в отраслевых стандартах вместе с #if, #ifdef, #else, а несколько других макросов - это, в лучшем случае, чрезмерный оператор, но может поддерживаться с явной ссылкой на конкретные отраслевые стандарты (но эти стандарты не включают ISO/IEC 9899: 2011 - стандарт C).


Обратите внимание, что Дэвид Хаммен предоставил информацию об одном конкретном стандарте кодирования C - Стандарт кодирования JPL C - это запрещает многие вещи, которые многие люди используют на C, включая ограничение использования препроцессора C (и ограничение использования динамического выделение памяти и запрещение рекурсии - прочитайте ее, чтобы понять, почему и решаете, относятся ли эти причины к вам).

Ответ 2

Нет, использование макросов не запрещено.

Фактически использование защитных устройств #include в файлах заголовков является одним из распространенных методов, которые часто являются обязательными и поощряются принятыми правилами кодирования. Некоторые люди утверждают, что #pragma once является альтернативой этому, но проблема в том, что #pragma once - по определению, поскольку прагмы являются крючком, предусмотренным стандартом для расширений, специфичных для компилятора, - является нестандартным, даже если он поддерживается несколькими компиляторами.

Тем не менее, существует ряд отраслевых рекомендаций и поощряется практика, которая активно препятствует использованию макросов, отличных от защитников #include, из-за появления макросов проблемы (не соблюдая область действия и т.д.). В С++-разработке использование макросов недооценивается еще сильнее, чем в разработке C.

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

Ответ 3

Некоторые стандарты кодирования могут препятствовать или даже запрещать использование #define для создания функционально-подобных макросов, которые принимают аргументы, например

#define SQR(x) ((x)*(x))

потому что a) такие макросы не являются безопасными для типов, и b) кто-то неизбежно напишет SQR(x++), что плохой juju.

Некоторые стандарты могут препятствовать или запрещать использование #ifdef для условной компиляции. Например, следующий код использует условную компиляцию для правильной печати значения size_t. Для C99 и позже вы используете спецификатор преобразования %zu; для C89 и ранее вы используете %lu и задаете значение unsigned long:

#if __STDC_VERSION__ >= 199901L
#  define SIZE_T_CAST
#  define SIZE_T_FMT "%zu"
#else
#  define SIZE_T_CAST (unsigned long)
#  define SIZE_T_FMT "%lu"
#endif
...
printf( "sizeof foo = " SIZE_T_FMT "\n", SIZE_T_CAST sizeof foo );

Некоторые стандарты могут утверждать, что вместо этого вы дважды реализуете модуль, один раз для C89 и ранее, один раз для C99 и более поздних версий:

/* C89 version */
printf( "sizeof foo = %lu\n", (unsigned long) sizeof foo );

/* C99 version */
printf( "sizeof foo = %zu\n", sizeof foo );

а затем Make (или Ant или какой-либо инструмент сборки, который вы используете) касаются компиляции и компоновки правильной версии. Для этого примера это было бы смехотворным излишеством, но я видел код, который был немаркированным крысиным гнездом #ifdef, который должен был иметь этот условный код, укомплектованный в отдельные файлы.

Тем не менее, я не знаю ни одной компании или группы компаний, которая запретила использование инструкций препроцессора.

Ответ 4

Макросы не могут быть "запрещены". Это абсурд. В буквальном смысле.

Например, в разделе 7.5 Ошибки <errno.h> стандарта C > требуется использование макросов:

1 Заголовок <errno.h> определяет несколько макросов, все относящиеся к отчету об ошибках.

2 Макросы

EDOM
EILSEQ
ERANGE

которые расширяются до целочисленных константных выражений с типом int, различными положительные значения и которые подходят для использования в #if предварительной обработке директивы; и

errno

который расширяется до изменяемого lvalue, имеющего тип int и thread время локального хранения, значение которого установлено на положительную ошибку число по нескольким библиотечным функциям. Если определение макроса подавляется для доступа к реальному объекту, или программа определяет идентификатор с именем errno, поведение undefined.

Таким образом, не только макросы являются обязательной частью C, в некоторых случаях их использование не приводит к поведению undefined.

Ответ 5

Нет, #define не запрещен. Неправильное использование #define, однако, может быть неодобрительно.

Например, вы можете использовать

#define DEBUG

в вашем коде, так что позже вы можете назначить части своего кода для условной компиляции с помощью #ifdef DEBUG только для целей отладки. Я не думаю, что кто-то в здравом уме хотел бы запретить что-то подобное. Макросы, определенные с помощью #define, также широко используются в переносных программах, чтобы включить/отключить компиляцию кода, специфичного для платформы.

Однако, если вы используете что-то вроде

#define PI 3.141592653589793

ваш учитель может по праву отметить, что гораздо лучше объявить PI как константу с соответствующим типом, например,

const double PI = 3.141592653589793;

поскольку он позволяет компилятору выполнять проверку типов при использовании PI.

Аналогично (как упоминалось выше, Джон Бод), использование макросов, подобных функциям, может быть отклонено, особенно на С++, где могут использоваться шаблоны. Поэтому вместо

#define SQ(X) ((X)*(X))

рассмотрим использование

double SQ(double X) { return X * X; }

или, в С++, еще лучше,

template <typename T>T SQ(T X) { return X * X; }

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

Как только у вас будет достаточно опыта в кодировании, вы точно узнаете, когда использовать #define. До тех пор, я думаю, это хорошая идея для вашего учителя навязать определенные правила и стандарты кодирования, но предпочтительно они сами должны знать и объяснять причины. Полный запрет на #define бессмыслен.

Ответ 6

Это абсолютно неверно, макросы сильно используются в C. Начинающие часто используют их плохо, но это не повод запретить их из промышленности. Классическое плохое использование #define succesor(n) n + 1. Если вы ожидаете, что 2 * successor(9) даст 20, то вы ошибаетесь, потому что это выражение будет переведено как 2 * 9 + 1 i.e. 19 not 20. Используйте скобки для получения ожидаемого результата.

Ответ 7

Нет. Он не запрещен. И, как говорят, невозможно сделать нетривиальный многоплатформенный код без него.

Ответ 8

Нет, ваш профессор ошибается или вы что-то слышали.

#define - макрос препроцессора, а макросы препроцессора необходимы для условной компиляции и некоторых соглашений, которые не просто построены на языке C. Например, в недавнем стандарте C, а именно C99, была добавлена ​​поддержка булевых элементов. Но он не поддерживает "родной" язык, а препроцессором #define s. См. эта ссылка на stdbool.h

Ответ 9

Макросы довольно широко используются в GNU land C, и без условных команд препроцессора невозможно правильно обрабатывать несколько включений одних и тех же исходных файлов, поэтому они кажутся мне важными языковыми функциями.

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

Ответ 10

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

Здесь только один пример: 16.1.1 Использовать препроцессор только для реализации include guard и включать файлы заголовков с включенными защитами.

Другой пример, на этот раз для C, а не для С++: Стандарт стандартного кодирования JPL для языка программирования C. Этот стандарт C-кодирования не идет настолько далеко, насколько полностью запрещает использование препроцессора, но он приближается. В частности, говорится:

Правило 20 (использование препроцессора) Использование препроцессора C должно ограничиваться включением файлов и простыми макросами. [Сила десяти Правила 8].


Я не потворствую и не осуждаю эти стандарты. Но сказать, что они не существуют, смешно.

Ответ 11

Если вы хотите, чтобы ваш код C взаимодействовал с кодом С++, вам нужно объявить свои внешне видимые символы, такие как объявления функций, в пространстве имен extern "C". Это часто делается с использованием условной компиляции:

#ifdef __cplusplus
extern "C" {
#endif

/* C header file body */

#ifdef __cplusplus
}
#endif

Ответ 12

Посмотрите на любой файл заголовка, и вы увидите что-то вроде этого:

#ifndef _FILE_NAME_H
#define _FILE_NAME_H
//Exported functions, strucs, define, ect. go here
#endif /*_FILE_NAME_H */

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

Компилятор также будет использовать define, как показано здесь с gcc, который позволит вам протестировать такие вещи, как версия компилятора, которая очень полезна. В настоящее время я работаю над проектом, который нужно скомпилировать с помощью avr-gcc, но у нас есть тестовая среда, в которой мы также запускаем наш код. Чтобы файлы avr и регистры не сохраняли наш тестовый код, мы делаем что-то вроде этого:

#ifdef __AVR__
//avr specific code here
#endif

Используя это в производственном коде, дополнительный тестовый код может компилироваться без использования avr-gcc, а приведенный выше код только скомпилирован с использованием avr-gcc.

Ответ 13

Если бы вы только что упоминали #define, я бы подумал, может быть, он ссылался на его использование для перечислений, которые лучше использовать enum, чтобы избежать глупых ошибок, например, дважды присвоить такое же числовое значение.

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

Однако добавление того, что #if, #ifdef и т.д. не должно использоваться, либо просто странно. Конечно, их, вероятно, не следует злоупотреблять, но в реальной жизни существуют десятки причин их использовать.

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

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