Понимание связи идентификаторов - PullRequest
0 голосов
/ 01 декабря 2018

Я читаю Стандарт : N1570 и натолкнулся на некоторое недопонимание.Я написал следующий простой пример:

test.h:

#ifndef TEST_H
#define TEST_H

extern int second;

#endif //TEST_H

test.c:

#include "test.h"

enum test_enum{ 
    first,
    second
};

Но он не компилируется с ошибкой:

error: ‘second’ redeclared as different kind of symbol
     second
     ^~~~~~

Это странно, поскольку в разделе 6.4.4.3#2 указано:

2 Идентификатор, объявленный как константа перечисления, имеет тип int.

I нашВ случае, если константа перечисления имеет область видимости файла, я ожидал, что она будет хорошо скомпилирована.

Я переписал приведенный выше пример следующим образом: main.c:

extern int second;
int main(int argc, char const *argv[])
{
    printf("Second: %d\n", second);
}

А теперь компоновщик жалуется:

undefined reference to `second'

Почему?Он должен найти определение в test.c, поскольку в качестве Section 6.2.2#5 указано:

Если объявление идентификатора для объекта имеет область файла и не имеет спецификатора класса хранения, его связь является внешней.

Ответы [ 2 ]

0 голосов
/ 01 декабря 2018

Объекты и константы - разные вещи

Ваша ссылка на 6.4.4.3 2, что константа перечисления имеет тип int, предполагает, что вы думаете, что, поскольку extern int second и enum { second } объявляют second вint, что эти два объявления second могут относиться к одному и тому же.Это неверно.

extern int second объявляет second именем объекта (области памяти), который будет содержать int.enum { second } объявляет second константой перечисления, которая будет иметь конкретное значение.Константа перечисления не имеет никакого объекта (без памяти), связанного с ней.Объект int и константа int - это разные вещи, и вы не можете использовать один и тот же идентификатор для них в одной и той же области видимости.

Не все объявления являются определениями

Относительно вашего вопросачто касается ошибки ссылки, «неопределенная ссылка на« second »», хотя test.c может содержать external int second (поскольку он предоставляется включенным test.h, это не определение second. Это всего лишь объявление, который сообщает компилятору, что имя относится к объекту. Он не определяет объект. В качестве альтернативы, если test.c содержит enum { second }, это только объявляет second константой. Он не определяет объект.

Правила того, что является определением, немного сложны из-за истории разработки языка программирования.Для идентификаторов объектов, объявленных в области видимости файла, существует по существу четыре случая:

  • Aобъявление с extern является только объявлением, а не определением. Пример: extern int second;.
  • Объявление с инициализатором является определениемПример: int second = 2;.
  • Предварительное определение - объявление без extern и без инициализатора.Если в модуле перевода нет определения (скомпилированный исходный файл со всеми включенными файлами), предварительное определение становится определением.Пример: int second;.

Связь здесь не полезна.extern int second в test.c и extern int second в main.c могут ссылаться на один и тот же объект из-за связи, но не было определено ни одного объекта, на который они могли бы ссылаться.Или, в качестве альтернативы, если test.c содержит enum { second }, то он не определяет объект с именем second, поэтому нет объекта, на который extern int second в main.c может ссылаться.

0 голосов
/ 01 декабря 2018

Чтобы сделать то, что вы пытаетесь, вам просто нужно немного изменить код, чтобы test.[ch] не видел переопределение second.Проблема в том, что символ second определяется один раз как extern int second;, а затем снова как символ внутри enum.Вы не можете видеть оба в одном и том же файле.

Для этого вы можете написать test1.h, используя второй условный препроцессор, подобный:

#ifndef TEST_H
#define TEST_H

#ifdef USE_ENUM
    enum test_enum{
        first,
        second
    };
#else
    extern int second;
#endif

#endif

Где в зависимости от того, USE_ENUM определяется, код будет использовать либо символ, предоставленный enum, где, если нет, то вам необходимо определить second в test1.c

#include "test1.h"

#ifdef USE_ENUM
    char stub (void)    /* stub to prevent empty compilation unit */
    { return 0; }
#else
    int second = 2;
#endif

(обратите внимание на использование stub функции для предотвращения пустой единицы компиляции , если определено USE_ENUM - иначе в test1.c не было бы кода)

Теперь все этотребуется включить test1.h в ваш файл, содержащий main() и передать компилятору, определив -DUSE_ENUM как параметр компилятора, в зависимости от того, какой код вы хотите использовать, например,

#include <stdio.h>
#include "test1.h"

int main (void) {

    printf ("second: %d\n", second);

}

CompileИспользование int second, определенное в test.c

Пример:

$ gcc -Wall -Wextra -pedantic -std=c11 -o bin/main1 main1.c test1.c

Пример Использование / Вывод

Когда USE_ENUMне определено, тогда определение second, определенное в test1.c и доступное через extern, приведет к second, имеющему значение 2, например

$ ./bin/main1
second: 2

Компиляция с использованием enum, определенным в test.h

Пример:

$ gcc -Wall -Wextra -pedantic -std=c11 -o bin/main1 main1.c test1.c -DUSE_ENUM

ПримерИспользование / Вывод

Когда определено USE_ENUM, значение для символа second предоставляется enum в test1.h, например,

$ ./bin/main1
second: 1

Хотя это незначительный рефакторинг того, что вы пытались сделать, я не вижу другого способа сделать оба без использования условного препроцессора.

...