Разрешение «переопределения» объекта с нулевой инициализацией с внутренней связью

Я разрабатываю микро-фреймворк для модульного тестирования и хочу предоставить клиенту возможность определить «имя набора тестов». Итак, у меня есть следующий заголовочный файл с именем 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):

For an identifier declared with the storage-class specifier extern in a scope in which a prior declaration of that identifier is visible,31) if the prior declaration specifies internal or external linkage, the linkage of the identifier at the later declaration is the same as the linkage specified at the prior declaration.

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

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

Выводит следующее сообщение об ошибке:

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

ДЕМО

  1. https://ideone.com/:

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

ДЕМО

  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)(выделение мое):

If the declaration of an identifier for a function has no storage-class specifier, its linkage is determined exactly as if it were declared with the storage-class specifier extern. If the declaration of an identifier for an object has file scope and no storage-class specifier, its linkage is external.

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

Вы можете ознакомиться с обсуждением здесь: gcc.gnu.org/bugzilla/show_bug.cgi?id=45977

Stephan Schlecht 29.05.2019 21:47

@StephanSchlecht Хороший улов. Вопрос касается именно того случая, который я рассматриваю. Вероятно, замена extern на static является обходным путем.

Some Name 29.05.2019 22:31

@StephanSchlecht Но полный отказ от спецификатора класса хранения - это UB. Есть ли способ сделать предупреждение в таком случае? -Wall -Wextra -pedantic не излучает.

Some Name 29.05.2019 22:36

1. coliru ДЕМО, вероятно, нуждается в -x c, чтобы сообщить компилятору, что исходный файл написан на C. 2. Кажется, что ideone не выдает предупреждения компилятора, но это означает, что компилятор не выдает никаких предупреждений.

cpplearner 30.05.2019 07:20

@cpplearner ты прав.

Some Name 30.05.2019 15:04

@SomeName Возможно, вы действительно сможете использовать статический вместо внешний. Однако, если вы опустите спецификатор статический, это должно быть даже ошибкой времени компиляции, а не просто предупреждением, или?

Stephan Schlecht 30.05.2019 19:21

@StephanSchlecht Да, static должно быть хорошо, поскольку разногласий по поводу связи нет. Никакой указанный класс хранения не компилируется без предупреждений, даже если это UB (несогласие с компоновкой)

Some Name 30.05.2019 21:35

Я быстро попробовал это с gcc на Ubuntu, см. ответ ниже. У меня выдает ошибку или я что-то не так понял?

Stephan Schlecht 30.05.2019 22:39
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
8
112
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

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

На самом деле вы можете использовать static вместо external. Делаем быстрый тест с помощью 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

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

Some Name 31.05.2019 03:46

Да, ты прав. Потребитель вашего фреймворка должен нет опустить static, потому что тогда поведение не определено из-за стандарта, которого, как правило, лучше избегать. Здесь применяется §6.2.2(7): Если в единице перевода один и тот же идентификатор появляется как с внутренней, так и с внешней связью, поведение не определено. Также наверняка существуют компиляторы C, которые скомпилируют это без сообщения об ошибке. Но не будет ли это проблемой со стороны потребителя?

Stephan Schlecht 31.05.2019 19:43

Другие вопросы по теме