Сводка
Как только вы определите статическую функцию foo
в единице перевода, foo
относится к этой функции для остальной части единицы перевода, за исключением того, что она может быть скрыта нефункциональной функцией.(например, определение объекта или типа) с именем foo
для части единицы перевода.Он не будет ссылаться на внешнюю функцию с именем foo
.
. При использовании объявлений, как описано ниже, идентификатор в теории может ссылаться на функцию из другой единицы перевода после * 1011.* объявление того же имени в этом переводе.К сожалению, поведение не определено стандартом C из-за C 2018 6.2.2 7:
Если внутри единицы перевода появляется одинаковый идентификатор как с внутренней, так и с внешней связью, поведениене определено.
Это означает, что вы не можете полагаться только на стандарт C, чтобы гарантировать это поведение, но реализация C может определить его как расширение.
Подробности
На эти вопросы отвечают правила С. для области видимости и связи.
Предположим, в File1.c
у нас есть статическое определение функции:
static int foo(int x) { return x*x; }
Поскольку объявлен идентификатор foo
вне какой-либо функции он имеет область действия файла (C 2018 6.2.1 4).Это означает, что идентификатор foo
видим и обозначает это определение функции для оставшейся части File1.c
.Кроме того, поскольку использовался static
, он имеет внутреннюю связь (6.2.2 3).
Исключение составляет область действия.Для областей внутри других областей, таких как блок { … }
, который определяет функцию внутри файла, или блок внутри блока, объявление того же идентификатора может скрывать внешнее объявление.Итак, давайте рассмотрим повторное выделение foo
внутри блока.
Чтобы сослаться на foo
, определенный вне File1.c
, нам нужно объявить foo
с внешней связью, чтобы этот новый foo
может быть связано с внешним определением foo
.Есть ли способ сделать это в C?
Если мы попытаемся объявить extern int foo(int x);
внутри блока, то применяется 6.2.2 4:
Для идентификатора, объявленного с помощьюспецификатор класса хранения extern
в области видимости, в которой видно предыдущее объявление этого идентификатора, если в предыдущем объявлении указана внутренняя или внешняя связь, связь идентификатора в более позднем объявлении такая же, как и связь, указанная в предыдущемобъявление.
Таким образом, это объявление будет просто повторно объявить то же самое foo
.
Если мы объявим это без extern
, применяется int foo(int x);
, 6.2.2 5 применяется:
Если объявление идентификатора для функции не имеет спецификатора класса хранения, ее связь определяется точно так, как если бы она была объявлена с помощью спецификатора класса хранения extern
.
Итак, похоже, что мы не можем объявить другой foo с extern
или без него.Но подождите, у нас есть еще один трюк.Мы можем сделать предыдущее объявление, которое указывает внутреннюю или внешнюю связь невидимым, скрыв его с объявлением без связи.Чтобы получить объявление без привязки, мы можем объявить объект (а не функцию) без extern
:
#include <stdio.h>
static int foo(int x) { return x*x; }
void bar(void)
{
int foo; // Not used except to hide the function foo.
{
extern int foo(int x);
printf("%d\n", foo(3));
}
}
Поскольку, где появляется extern int foo(int x);
, предшествующее объявление foo
с внутреннимсвязь не видна, что первое условие в 6.2.2 4, приведенное выше, не применяется, а остальное в 6.2.2 4:
, если никакое предыдущее объявление не видно, или если предыдущее объявлениене указывает никакой связи, тогда идентификатор имеет внешнюю связь.
Это «допустимый» C-код.К сожалению, он не определен в 6.2.2 7:
Если в единице перевода появляется один и тот же идентификатор как с внутренней, так и с внешней связью, поведение не определено.