Разрешение переопределения объекта с нулевой инициализацией с внутренней связью - PullRequest
2 голосов
/ 29 мая 2019

Я разрабатываю микро-фреймворк для модульного тестирования и хочу, чтобы у клиента была возможность определить «имя набора тестов». Итак, у меня есть следующий заголовочный файл с именем test_suite.h:

static const char *const test_suite_name;

static inline void run_all_tests(void){
    printf("Running ");
    if(!test_suite_name){
        printf("unnamed suite");
    } else {
        printf("%s suite", test_suite_name);
    }
    //run tests
}

Цель этого состоит в том, чтобы позволить клиентам "переопределить" test_suite_name следующим образом:

#include "test_suite.h"

extern const char *const test_suite_name = "suite1";

Я думаю, поведение такого использования четко определено , поскольку static const char *const test_suite_name; представляет собой предварительное определение, а затем extern const char *const test_suite_name = "suite1"; представляет собой внешнее определение. Нет разногласий по поводу связи с 6.2.2(p4):

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

Я провел несколько экспериментов:

  1. https://coliru.stacked -crooked.com

Распечатывает следующее сообщение об ошибке:

error: redefinition of 'const char* const suite_name'
 extern const char *const suite_name = "some suite";

DEMO

  1. https://ideone.com/:

Работает совершенно нормально, без предупреждений

DEMO

  1. gcc7.4.0 на моей машине.

Выдает предупреждение:

warning: ‘test_suite_name’ initialized and declared ‘extern’

Вопрос: Является ли поведение кода, показанного выше, хорошо определенным?

Я почти уверен, что поведение будет неопределенным, если написать следующее:

#include "test_suite.h"

const char *const test_suite_name = "suite1"; //without extern

из-за 6.2.2(p5) (подчеркните мой):

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

Таким образом, у нас было бы несоответствие между static const char *const test_suite_name; с внутренней связью и const char *const test_suite_name = "suite1"; с внешней связью.

1 Ответ

2 голосов
/ 30 мая 2019

Использование статического

Возможно, вы сможете использовать статический вместо внешнего. Быстрое тестирование с помощью gcc под Ubuntu:

#include "test_suite.h"

static const char *const test_suite_name = "huhu";

int main() {
      run_all_tests();
      return 0;
}

Если я скомпилирую с:

gcc -Wall -Wpedantic -Wextra mytest.c -o mytest

это дает как вывод:

Running huhu suite

Пропуск статического

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

const char *const test_suite_name = "huhu";

и попробуйте скомпилировать его так:

gcc -Wall -Wpedantic -Wextra mytest2.c -o mytest2

будет отображаться это сообщение об ошибке:

mytest2.c:3:19: error: non-static declaration of ‘test_suite_name’ follows static declaration
 const char *const test_suite_name = "huhu";
                   ^~~~~~~~~~~~~~~
In file included from mytest2.c:1:
test_suite.h:3:26: note: previous declaration of ‘test_suite_name’ was here
 static const char *const test_suite_name;

Поскольку это ошибка, она также выводится, если вы компилируете:

gcc mytest2.c -o mytest2

Снимок экрана с сообщением об ошибке

Screenshot with Error Message

...