Статические функции, объявленные в заголовочных файлах "C"
Для меня это правило для определения и объявления статических функций внутри исходных файлов, я имею в виду .c файлы.
Однако в очень редких ситуациях я видел людей, объявляющих его в заголовочном файле.
Поскольку статические функции имеют внутреннюю связь, мы должны определить ее в каждом файле, который мы включаем в заголовочный файл, где объявлена функция. Это выглядит довольно странно и далеко от того, что мы обычно хотим, когда объявляем что-то статичным.
С другой стороны, если кто-то наивный попытается использовать эту функцию без определения этого компилятор будет жаловаться. Так что в каком-то смысле не очень небезопасно это делать, даже звуча странно.
Мои вопросы:
- в чем проблема объявления статических функций в заголовочных файлах?
- каковы риски?
- каково влияние во время компиляции?
- существует ли какой-либо риск во время выполнения?
3 ответов:
Сначала я хотел бы прояснить свое понимание ситуации, которую вы описываете: заголовок содержит (только) объявление статической функции, в то время как файл C содержит определение, т. е. исходный код функции. Например
Некоторые.h:
static void f(); // potentially more declarationsНекоторые.c:
#include "some.h" static void f() { printf("Hello world\n"); } // more code, some of it potentially using f()Если это та ситуация, которую вы описываете, я не согласен с вашим замечанием
Так как статические функции имеют внутреннюю связь, мы должны определить ее в каждом файле, который мы включаем. заголовочный файл, в котором объявлена функция.
Если вы объявляете функцию, но не используете ее в данной единице перевода, я не думаю, что вам нужно ее определять. gcc принимает это с предупреждением; стандарт, похоже, не запрещает этого, если только я не пропустил что-то. Это может быть важно в вашем сценарии, потому что единицы перевода, которые не используют функцию, но включают заголовок с его объявлением, не должны предоставлять неиспользуемое определение.
Теперь давайте рассмотрим вопросы:
в чем проблема объявления статических функций в заголовочных файлах?
это несколько необычно. Это имело бы смысл только в том случае, если большинство единиц перевода, которые включают заголовок с заданным объявлением функции, действительно используют эту функцию, потому что основное обоснование и преимущество статической функции-их ограниченная видимость. Они не загрязняют глобальное пространство имен (единственное, которое имеет C) и могут использоваться как "частные" методы бедняка, которые не предназначены для быть использованы широкой публикой и, следовательно, объявлены такими, что они доступны только там, где они необходимы.С другой стороны, на самом деле может быть полезно иметь объявление в заголовке, поскольку это гарантирует, что все локальные определения согласуются, по крайней мере, в сигнатуре функции. (Две функции с одинаковым именем, но разными типами возвращаемых значений вызвали бы ошибку времени компиляции в C (и C++); различные типы параметров вызвали бы ошибку времени компиляции только в C, поскольку она не имеет перегрузка функций.) С этой точки зрения единообразия может быть достаточно сразу же предоставить определение функции, соответствующее заголовочному файлу, если предполагается, что функция идентична в каждой единице перевода. Накладные расходы этого подхода зависят от того, действительно ли все единицы перевода, включающие заголовок, используют эту функцию.
каковы риски?
я не вижу рисков в вашем сценарии. (В отличие от также включения функции определение в заголовке, которое может нарушать принцип инкапсуляции.)каково влияние во время компиляции?
объявление функции невелико, а ее сложность невелика, поэтому накладные расходы на дополнительные объявления функций в заголовке, скорее всего, незначительны. Но если вы создадите и включите дополнительный заголовок для объявления во многих единицах перевода, то затраты на обработку файлов могут быть значительными (т. е. компилятор много простаивает пока он ждет ввода-вывода заголовка).есть ли риск во время выполнения?
я ничего не вижу.
Это не ответ на поставленные вопросы, но, надеюсь, показывает , Почему можно реализовать функцию
static(илиstatic inline) в заголовочном файле.Лично я могу назвать только две веские причины для объявления некоторых функций
staticв заголовочном файле:
Если заголовочный файл полностью реализует интерфейс, который должен быть виден только в текущем блоке компиляции
Это крайне редко, но может быть полезно, например, в образовательных целях. контекст, в какой-то момент во время разработки некоторой библиотеки примеров; или, возможно, при взаимодействии с другим языком программирования с минимальным кодом.
Разработчик может сделать это, если библиотека или реализация interaface тривиальны и почти таковы, а простота использования (для разработчика, использующего файл заголовка) важнее размера кода. В этих случаях объявления в заголовочном файле часто используют макросы препроцессора, что позволяет включать один и тот же заголовочный файл более чем в два раза. однажды, обеспечивая какой-то грубый полиморфизм в C.Вот практический пример: стреляй-себе-в-ногу площадка для линейных конгруэнтных генераторов псевдослучайных чисел. Поскольку реализация является локальной для блока компиляции, каждый блок компиляции получит свои собственные копии PRNG. Этот пример также показывает, как грубый полиморфизм может быть реализован в C.
Прнг32.h :
#if defined(PRNG_NAME) && defined(PRNG_MULTIPLIER) && defined(PRNG_CONSTANT) && defined(PRNG_MODULUS) #define MERGE3_(a,b,c) a ## b ## c #define MERGE3(a,b,c) MERGE3_(a,b,c) #define NAME(name) MERGE3(PRNG_NAME, _, name) static uint32_t NAME(state) = 0U; static uint32_t NAME(next)(void) { NAME(state) = ((uint64_t)PRNG_MULTIPLIER * (uint64_t)NAME(state) + (uint64_t)PRNG_CONSTANT) % (uint64_t)PRNG_MODULUS; return NAME(state); } #undef NAME #undef MERGE3 #endif #undef PRNG_NAME #undef PRNG_MULTIPLIER #undef PRNG_CONSTANT #undef PRNG_MODULUSПример использования приведенного выше, пример-prng32.h :
#include <stdlib.h> #include <stdint.h> #include <stdio.h> #define PRNG_NAME glibc #define PRNG_MULTIPLIER 1103515245UL #define PRNG_CONSTANT 12345UL #define PRNG_MODULUS 2147483647UL #include "prng32.h" /* provides glibc_state and glibc_next() */ #define PRNG_NAME borland #define PRNG_MULTIPLIER 22695477UL #define PRNG_CONSTANT 1UL #define PRNG_MODULUS 2147483647UL #include "prng32.h" /* provides borland_state and borland_next() */ int main(void) { int i; glibc_state = 1U; printf("glibc lcg: Seed %u\n", (unsigned int)glibc_state); for (i = 0; i < 10; i++) printf("%u, ", (unsigned int)glibc_next()); printf("%u\n", (unsigned int)glibc_next()); borland_state = 1U; printf("Borland lcg: Seed %u\n", (unsigned int)borland_state); for (i = 0; i < 10; i++) printf("%u, ", (unsigned int)borland_next()); printf("%u\n", (unsigned int)borland_next()); return EXIT_SUCCESS; }Причина пометки как переменной
_state, так и функции_next()staticзаключается в том, что таким образом каждая единица компиляции, включающая файл заголовка, имеет свою собственную копию переменных и функций-здесь свою собственную копию PRNG. Каждый из них, конечно, должен быть посеян отдельно; и если посеять его с одинаковой величиной, то получится та же последовательность.Следует вообще избегать таких попыток полиморфизма в С, потому что это приводит к сложные макропроцессорные махинации с препроцессором делают реализацию намного сложнее понять, поддерживать и модифицировать, чем это необходимо.
Однако, когда исследует пространство параметров некоторого алгоритма-как здесь, типы 32-битных линейных конгруэнтных генераторов, это позволяет нам использовать одну реализацию для каждого из рассматриваемых генераторов, гарантируя отсутствие различий в реализации между ними. Заметьте, что даже этот случай больше похож на развитие событий инструмент, а не то, что вы должны видеть в реализации, предоставленной для использования другими.
Если заголовок реализует простые
Макросы препроцессора обычно используются для упрощения кода, обращающегося к сложным типам структур.static inlineфункции доступаstatic inlineфункции похожи, за исключением того, что они также обеспечивают проверку типов во время компиляции и могут ссылаться на свои параметры несколько раз (с помощью макросов, что проблематично).Один практический пример использования-это простой интерфейс для чтения файлов с использованием низкоуровневого POSIX.1 ввод / вывод (используя
<unistd.h>и<fcntl.h>вместо<stdio.h>). Я сам делал это при чтении очень больших (от десятков мегабайт до гигабайт) текстовых файлов, содержащих вещественные числа (с пользовательским парсером float/double), поскольку стандартный ввод-вывод GNU C не особенно быстр.Например, inbuffer.h :
#ifndef INBUFFER_H #define INBUFFER_H typedef struct { unsigned char *head; /* Next buffered byte */ unsigned char *tail; /* Next byte to be buffered */ unsigned char *ends; /* data + size */ unsigned char *data; size_t size; int descriptor; unsigned int status; /* Bit mask */ } inbuffer; #define INBUFFER_INIT { NULL, NULL, NULL, NULL, 0, -1, 0 } int inbuffer_open(inbuffer *, const char *); int inbuffer_close(inbuffer *); int inbuffer_skip_slow(inbuffer *, const size_t); int inbuffer_getc_slow(inbuffer *); static inline int inbuffer_skip(inbuffer *ib, const size_t n) { if (ib->head + n <= ib->tail) { ib->head += n; return 0; } else return inbuffer_skip_slow(ib, n); } static inline int inbuffer_getc(inbuffer *ib) { if (ib->head < ib->tail) return *(ib->head++); else return inbuffer_getc_slow(ib); } #endif /* INBUFFER_H */Обратите внимание, что приведенные выше
inbuffer_skip()иinbuffer_getc()не проверяют, является лиibненулевым; это типично для таких функций. Предполагается, что эти функции доступа являются "в быстром пути", т. е. вызываются очень часто. В таких случаях даже накладные расходы на вызов функции имеют значение (и избегаются функциямиstatic inline, поскольку они дублируются в коде на месте вызова).Тривиальные функции доступа, такие как вышеупомянутые
Лично я рекомендую сначала написать несколько тестовых программ, использующих не встроенные функции, и сравнить производительность и результаты с встроенными версиями. Сравнение результатов гарантирует, что встроенные версии не имеют ошибок (выкл по одному типу здесь распространен!), и сравнение производительности и сгенерированных двоичных файлов (по крайней мере, размер) говорит вам стоит ли вообще вкладывать деньги.inbuffer_skip()иinbuffer_getc(), также могут позволить компилятору избежать перемещения регистров, участвующих в вызовах функций, поскольку функции ожидают, что их параметры будут расположены в определенных регистры или в стеке, тогда как встроенные функции могут быть адаптированы (wrt. зарегистрируйте использования), в код, окружающие подставляемые функции.
Зачем вам нужна как глобальная, так и статическая функция? В языке c функции по умолчанию являются глобальными. Статические функции используются только в том случае, если требуется ограничить доступ к функции объявленным файлом. Поэтому вы активно ограничиваете доступ, объявляя его статическим...
Единственное требование для реализаций в заголовочном файле, это для функций шаблона c++ и функций-членов класса шаблона.
Comments