Почему определять «extern_» вместо использования «extern»?

#c #include #extern

#c #включить #внешний

Вопрос:

Я попытался создать простой лексер, я нашел эту страницу на github:https://github.com/DoctorWkt/acwj/tree/master/01_Scanner И в его исходном коде я видел, что:

data.h:

 ...
#ifndef extern_
 #define extern_ extern
#endif

extern_ int Line;
extern_ int Putback;
extern_ FILE *Infile;
...
  

main.c:

 ...
#define extern_
#include "data.h"
#undef extern_
...
  

Если я использую только ключевое слово extern, которое не работает, но оно работает с extern_, так в чем разница?

Комментарии:

1. обычно это делается, если вы хотите добавить еще что-то для других компиляторов или если у вас есть особые требования. Тогда вам нужно изменить только одно определение, а не 10000 объявлений . определения

2. Хорошо, но я не понимаю, почему в main.c мне нужно определить extern_ для включения данных.h @ P__J__

3. ни за что. Просто плохое кодирование

4. Вы знаете, как работает #define?

5. Не совсем, я должен признаться @user253751

Ответ №1:

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

На этом этапе важно отметить, что код, представленный в вопросе, не является репрезентативным для кода в проекте GitHub, на который ссылается. На GitHub объявления переменных отображаются в заголовке, а не в main.c . Это напрямую связано с тем, почему такое средство полезно.

В частности, тогда рассмотрите разницу между двумя альтернативами, используемыми в общем проекте:

  • Большинство исходных файлов C в проекте #include заголовок без определения макроса extern_ . В этих случаях сам заголовок определяет макрос, extern_ который будет расширяться до ключевого слова extern , что приводит к этим объявлениям в этих единицах перевода:

      extern int Line;
     extern int Putback;
     extern FILE *Infile;
      
  • Файл main.c особенный. Он определяет макрос extern_ для расширения до нуля и включает заголовок в область действия этого определения. Затем заголовок полагается на предоставленное определение макроса, так что только в этой единице перевода результирующие объявления являются

       int Line;
      int Putback;
      FILE *Infile;
      

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

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

Если я использую только ключевое слово extern, которое не работает, но оно работает с extern_, так в чем разница?

Объявление переменной extern без предоставления инициализатора представляет собой обещание, что переменная определена где-то в программе, но сама по себе не приводит к определению переменной. Если данная переменная не объявлена каким-либо другим способом нигде в программе, то все эти обещания не выполняются, и результирующее поведение не определено. Как правило, это проявляется в виде сбоя связи.

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

Наконец, я отмечаю, что вся эта история с extern_ макрокомандой является чем-то вроде взлома и не должна рассматриваться как обычная. Канонический способ сделать это заключается в том, чтобы в заголовке просто объявлялись все переменные extern и чтобы было отдельное определение каждой из них в выбранном исходном файле C (не обязательно все в одном файле). Пример:


data.h

 extern int Line;
extern int Putback;
extern FILE *Infile;
  

main.c

 #include "data.h"

int Line /* optionally with an initializer here */;
int Putback  /* optionally with an initializer here */;
FILE *Infile  /* optionally with an initializer here */;

// ...
  

другое.c

 #include "data.h"

// no (additional) declarations or definitions of the variables declared in data.h
  

Ответ №2:

#define extern_ сообщает препроцессору заменить extern_ ничем всякий раз, когда он это видит.

Так что в данном случае extern_ это ничего не значит.

Но я готов поспорить, что в других файлах они не используются #define extern_ . В этом случае активируется #define extern_ extern в файле заголовка, потому что #ifndef extern_ имеет значение true ( extern_ еще не определено). Это указывает препроцессору заменить extern_ на extern . Таким образом, в одном файле переменные определены без extern , а во всех других файлах они есть extern . (Почему это полезно? Если вы знаете, как extern работает, вы поймете почему)