Я разрабатываю микро-фреймворк для модульного тестирования и хочу предоставить клиенту возможность определить «имя набора тестов». Итак, у меня есть следующий заголовочный файл с именем 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.
Я провел несколько экспериментов:
Выводит следующее сообщение об ошибке:
error: redefinition of 'const char* const suite_name'
extern const char *const suite_name = "some suite";
Работает полностью нормально без предупреждений
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";
с внешней связью.
@StephanSchlecht Хороший улов. Вопрос касается именно того случая, который я рассматриваю. Вероятно, замена extern
на static
является обходным путем.
@StephanSchlecht Но полный отказ от спецификатора класса хранения - это UB. Есть ли способ сделать предупреждение в таком случае? -Wall -Wextra -pedantic
не излучает.
1. coliru ДЕМО, вероятно, нуждается в -x c
, чтобы сообщить компилятору, что исходный файл написан на C. 2. Кажется, что ideone не выдает предупреждения компилятора, но это означает, что компилятор не выдает никаких предупреждений.
@cpplearner ты прав.
@SomeName Возможно, вы действительно сможете использовать статический вместо внешний. Однако, если вы опустите спецификатор статический, это должно быть даже ошибкой времени компиляции, а не просто предупреждением, или?
@StephanSchlecht Да, static
должно быть хорошо, поскольку разногласий по поводу связи нет. Никакой указанный класс хранения не компилируется без предупреждений, даже если это UB (несогласие с компоновкой)
Я быстро попробовал это с gcc на Ubuntu, см. ответ ниже. У меня выдает ошибку или я что-то не так понял?
Использование статических
На самом деле вы можете использовать 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
Скриншот сообщения об ошибке
В любом случае, случай без указания класса хранения не является нарушением ограничений, поэтому диагностическое сообщение не требуется.
Да, ты прав. Потребитель вашего фреймворка должен нет опустить static, потому что тогда поведение не определено из-за стандарта, которого, как правило, лучше избегать. Здесь применяется §6.2.2(7): Если в единице перевода один и тот же идентификатор появляется как с внутренней, так и с внешней связью, поведение не определено. Также наверняка существуют компиляторы C, которые скомпилируют это без сообщения об ошибке. Но не будет ли это проблемой со стороны потребителя?
Вы можете ознакомиться с обсуждением здесь: gcc.gnu.org/bugzilla/show_bug.cgi?id=45977