C/С++: статическая функция в файле заголовка, что это значит?

Я знаю, что это означает, когда статическая функция объявлена ​​в исходном файле. Я читаю код, обнаружил, что статическая функция в файлах заголовков может быть вызвана в других файлах.

Ответ 1

Является ли функция определенной в файле заголовка? Чтобы фактический код был задан непосредственно в функции, например:

static int addTwo(int x)
{
  return x + 2;
}

Тогда это просто способ предоставить полезную функцию для многих разных файлов C. Каждый файл C, содержащий заголовок, получит свое собственное определение, которое он может вызвать. Это, конечно, отнимает память, и, на мой взгляд, это довольно уродливая вещь, поскольку исполняемый код в заголовке обычно не является хорошей идеей.

Помните, что #include: заголовок в основном просто вставляет содержимое заголовка (и любых других заголовков, включенных в него) в файл C, как видно компилятору. Компилятор никогда не знает, что одно конкретное определение функции появилось из файла заголовка.

ОБНОВЛЕНИЕ. Во многих случаях на самом деле неплохо сделать что-то подобное выше, и я понимаю, что мой ответ звучит очень черно-белое, что немного упрощает, Например, код, который моделирует (или просто использует) внутренние функции, может быть выражен как выше, так и с явным ключевым словом inline:

static inline int addTwo(int *x)
{
  __add_two_superquickly(x);
}

Здесь функция __add_two_superquickly() является вымышленным внутренним, и поскольку мы хотим, чтобы вся функция в основном скомпилировалась до одной инструкции, мы действительно хотим, чтобы она была встроена. Тем не менее, вышеупомянутое более чистое, чем использование макроса.

Преимущество только в том, что использование непосредственно встроенного - это, конечно, то, что обертка его на другом уровне абстракции позволяет построить код на компиляторах, лишенных этого конкретного внутреннего, путем предоставления альтернативной реализации и выбора правильного, в зависимости от того, какой компилятор используется.

Ответ 2

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

Ответ 3

Как говорят другие, он имеет то же значение, что и функция static в самом файле .c. Это связано с тем, что между .c и .h не существует семантической разницы; существует только блок компиляции, составленный из файла, фактически переданного компилятору (обычно с именем .c) с содержимым всех и всех файлов, названных в #include строках (обычно называемых .h), вставленных в поток, поскольку они видны препроцессором.

Соглашение о том, что источник C находится в файле с именем .c, а публичные объявления в файлах с именем .h - это только соглашение. Но это, как правило, хороший. В соответствии с этим соглашением единственными вещами, которые должны отображаться в файлах .h, являются объявления, так что вы обычно избегаете наличия одного и того же символа, определенного более одного раза в одной программе.

В этом конкретном случае ключевое слово static делает этот символ частным для модуля, поэтому конфликт с множественным определением не вызывает проблем. Поэтому в этом смысле это безопасно. Но в отсутствие гарантии того, что функция будет встроена, вы рискуете, что функция будет создана в каждом модуле, который произошел с #include, этот файл заголовка, который в лучшем случае представляет собой потерю памяти в сегменте кода.

Я не уверен, какие варианты использования оправдали бы это вообще в общедоступном публичном заголовке.

Если файл .h сгенерирован и включен только в один файл .c, я бы лично назвал файл чем-то отличным от .h, чтобы подчеркнуть, что он вообще не является публичным заголовком. Например, утилита, которая преобразует двоичный файл в определение инициализированной переменной, может записать файл, который предназначен для использования через #include, и вполне может содержать объявление static переменной и, возможно, даже static определений доступа или других связанных функций полезности.

Ответ 4

Не существует семантической разницы в определении в исходном файле или файле заголовка, в основном оба означают одно и то же в plain C при использовании статического ключевого слова, которое ограничивает область.

Однако при записи в заголовочном файле возникает проблема, потому что каждый раз, когда вы включаете заголовок в исходный файл, у вас будет копия функции с той же реализацией, которая очень похожа на обычную функцию определенных в файле заголовка. Добавляя определение в заголовке, вы не достигаете того, для чего предназначена статическая функция.

Поэтому я предлагаю вам иметь свою реализацию только в исходном файле, а не в заголовке.

Ответ 5

Это полезно в некоторых "только для заголовков" библиотек с небольшими встроенными функциями. В таком случае вы всегда хотите сделать копию функции, так что это не плохой шаблон. Однако это дает вам простой способ вставки отдельных частей интерфейса и реализации в один заголовочный файл:

// header.h

// interface part (for user?!)
static inline float av(float a, float b);

// implementation part (for developer)
static inline float av(float a, float b)
{
    return (a+b)/2.f;
}

Библиотека векторных векторов Apple в структуре GLK использует такое построение (например, GLKMatrix4.h).

Ответ 6

Если вы определяете функцию в файле заголовка (а не просто объявляете ее), копия функции будет сгенерирована в каждой единицы перевода (в основном в каждом файле cpp, который включает этот заголовок).

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

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

static int counter()
{
    static int ctr = 0;
    return ctr++;
}

Вместо

//header
int counter();

//source
int counter()
{
    static int ctr = 0;
    return ctr++;
}

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

Таким образом, говоря, что единственное различие будет производительностью, а размер кода неправильным.