extern 키워드가 C 함수에 미치는 영향


171

C에서는 extern함수 선언 전에 사용 된 키워드의 영향을 느끼지 못했습니다 . 처음에는 정의 할 때 생각 extern int f();단일 파일에 이 외부 파일의 범위를 구현 할 수 있습니다. 그러나 나는 두 가지를 발견했다.

extern int f();
int f() {return 0;}

extern int f() {return 0;}

gcc의 경고없이 잘 컴파일하십시오. 나는 사용했다 gcc -Wall -ansi; 심지어 //의견을 받아들이지 않을 것 입니다.

extern 이전 함수 정의 를 사용하는 데 영향이 있습니까? 또는 기능에 대한 부작용이없는 선택적인 키워드 일뿐입니다.

후자의 경우 표준 디자이너가 왜 불필요한 키워드로 문법을 버리려고했는지 이해가 가지 않습니다.

편집 : 내가 사용이 거기 알고, 명확히하기 위해 extern변수,하지만 난 단지에 대해 부탁 해요 extern에서 기능 .


내가 어떤 미친 템플릿 목적으로 이것을 사용하려고 시도했을 때 수행 한 일부 연구에 따르면 extern은 대부분의 컴파일러가 의도 한 형식으로 지원되지 않으므로 실제로는 아무것도하지 않습니다.
Ed James

4
항상 불필요한 것은 아니며 내 대답을 참조하십시오. 공용 헤더에서 원하지 않는 모듈간에 무언가를 공유해야 할 때마다 매우 유용합니다. 그러나 (공식 컴파일러와 함께) 공개 헤더의 모든 단일 함수를 '교환'하는 것은 독자적으로 알아낼 수 있기 때문에 이점이 거의 없거나 전혀 없습니다.
Tim Post

@Ed .. volatile int foo가 foo.c의 전역이고 bar.c에 필요하면 bar.c는 extern으로 선언해야합니다. 장점이 있습니다. 그 외에도 공용 헤더에 노출하고 싶지 않은 일부 기능을 공유해야 할 수도 있습니다.
Tim Post


2
@Barry 다른 질문은이 질문과 중복됩니다. 2009 년 대 2012 년
Elazar Leibovich

답변:


138

foo.c와 bar.c라는 두 파일이 있습니다.

여기 foo.c가 있습니다

#include <stdio.h>

volatile unsigned int stop_now = 0;
extern void bar_function(void);

int main(void)
{
  while (1) {
     bar_function();
     stop_now = 1;
  }
  return 0;
}

자, 여기 bar.c입니다

#include <stdio.h>

extern volatile unsigned int stop_now;

void bar_function(void)
{
   while (! stop_now) {
      printf("Hello, world!\n");
      sleep(30);
   }
}

보시다시피 foo.c와 bar.c 사이에 공유 헤더가 없지만 bar.c는 foo.c에 연결될 때 선언 된 것이 필요하고 foo.c는 bar.c에 연결될 때 함수가 필요합니다.

'extern'을 사용하면 컴파일러가 링크 타임에 다음과 같은 것이 발견 될 때 (정적이 아님) 알 수 있습니다. 현재 패스에서 나중에 예약 할 예정이므로 예약하지 마십시오. 함수와 변수는 이와 관련하여 동일하게 취급됩니다.

모듈간에 전역을 공유해야하고 헤더에 넣거나 초기화하지 않으려는 경우 매우 유용합니다.

기술적으로 라이브러리 공개 헤더의 모든 함수는 'extern'이지만 레이블을 지정하면 컴파일러에 따라 거의 이점이 없습니다. 대부분의 컴파일러는 스스로 알아낼 수 있습니다. 보시다시피 이러한 함수는 실제로 다른 곳에 정의되어 있습니다.

위의 예에서 main ()은 hello world를 한 번만 인쇄하지만 bar_function ()을 계속 입력합니다. 또한이 예제에서는 bar_function ()이 반환되지 않습니다 (단순한 예제이므로). 신호가 충분히 실용적이지 않으면 신호가 서비스 될 때 stop_now가 수정된다고 상상하십시오.

Externs는 신호 처리기, 헤더 또는 구조에 넣기를 원하지 않는 뮤텍스 등과 같은 것들에 매우 유용합니다. 대부분의 컴파일러는 외부 객체를 위해 메모리를 예약하지 않도록 최적화합니다. 객체가 정의 된 모듈에서 예약 할 것입니다. 그러나 공용 함수를 프로토 타이핑 할 때 최신 컴파일러로이를 지정하는 데는 별다른 의미가 없습니다.

희망이 있습니다 :)


56
bar_function 전에 extern없이 코드가 잘 컴파일됩니다.
Elazar Leibovich

2
@Tim : 그러면 내가 작업하는 코드로 작업하는 데 대한 의심스러운 특권이 없었습니다. 일어날 수 있습니다. 때로는 헤더에 정적 함수 정의도 포함되어 있습니다. 그것은 추악하고 불필요 한 시간의 99.99 %입니다 (필요한 빈도를 과장하여 주문 또는 2 또는 규모로 벗어날 수 있습니다). 일반적으로 사람들은 다른 소스 파일이 정보를 사용할 때만 헤더가 필요하다는 것을 오해 할 때 발생합니다. 헤더는 하나의 소스 파일에 대한 선언 정보를 저장하는 데 사용되며 다른 파일에는 포함하지 않을 것으로 예상됩니다. 때때로 더 왜곡 된 이유로 발생합니다.
Jonathan Leffler 2016 년

2
@Jonathan Leffler-참으로 의심스러운! 나는 전에 다소 스케치 코드를 물려 받았지만 정직하게 누군가가 헤더에 정적 선언을 넣는 것을 본 적이 없다고 말할 수 있습니다. 비록 당신이 다소 재미 있고 흥미로운 일을하는 것처럼 들립니다. :)
Tim Post

1
'헤더에없는 함수 프로토 타입'의 단점은의 함수 정의 bar.c와의 선언 사이의 일관성을 자동으로 독립적으로 확인할 수 없다는 것 입니다 foo.c. 함수가 선언 foo.h 되고 두 파일에 모두 포함 된 foo.h경우 헤더는 두 소스 파일 간의 일관성을 강제합니다. 그것 없이는 bar_functionin 의 정의가 bar.c변경되었지만 선언 foo.c이 변경되지 않으면 런타임에 문제가 발생합니다. 컴파일러는 문제를 발견 할 수 없습니다. 헤더를 올바르게 사용하면 컴파일러가 문제를 발견합니다.
Jonathan Leffler

1
extern on function 선언은 'unsigned int'의 'int'와 같이 불필요합니다. 프로토 타입이 정방향 선언이 아닌 경우 'extern'을 사용하는 것이 좋습니다. 그러나 작업이 엣지 케이스가 아닌 경우 실제로 'extern'이없는 헤더에 있어야합니다. stackoverflow.com/questions/10137037/…

82

표준을 기억하는 한 모든 함수 선언은 기본적으로 "extern"으로 간주되므로 명시 적으로 지정할 필요가 없습니다.

이 키워드는 변수와 함께 사용할 수 있기 때문에 쓸모가 없습니다 (이 경우 링크 문제를 해결하는 유일한 솔루션입니다). 그러나 기능을 사용하면 옵션입니다.


21
그런 다음 표준 디자이너 로서 문법에 노이즈를 추가하기 때문에 함수와 함께 extern을 사용하는 것을 허용하지 않습니다 .
Elazar Leibovich

3
이전 버전과의 호환성은 어려울 수 있습니다.
MathuSum Mut

1
@ElazarLeibovich 사실,이 특정한 경우 에는 문법에 노이즈를 추가하는 것이 허용되지 않습니다 .
궤도에서 가벼움 경주

1
키워드를 제한하여 노이즈를 추가 하는 방법 은 저를 넘어서지 만 맛의 문제인 것 같습니다.
Elazar Leibovich

함수에 "extern"을 사용하면 함수가 현재 파일이 아닌 다른 파일에 정의되어 있으며 포함 된 헤더 중 하나에도 선언되지 않았으므로 다른 함수에 "extern"을 사용할 수 있습니다.
DimP

23

함수 정의와 기호 선언이라는 두 가지 개별 개념을 구별해야합니다. "extern"은 링키지 수정 자이며, 나중에 참조되는 심볼이 정의 된 위치에 대한 컴파일러의 힌트입니다 (힌트는 "여기서는 안됩니다").

내가 쓰면

extern int i;

C 파일의 파일 범위 (함수 블록 외부)에서 "변수가 다른 곳에서 정의 될 수 있습니다"라고 말합니다.

extern int f() {return 0;}

함수 f의 선언과 함수 f의 정의입니다. 이 경우 정의는 extern을 대체합니다.

extern int f();
int f() {return 0;}

먼저 선언 다음에 정의가옵니다.

extern파일 범위 변수를 선언하고 동시에 정의하려는 경우 사용 이 잘못되었습니다. 예를 들어

extern int i = 4;

컴파일러에 따라 오류 또는 경고가 표시됩니다.

extern변수의 정의를 명시 적으로 피하려는 경우 사용법 이 유용합니다.

설명하겠습니다 :

ac 파일에 다음이 포함되어 있다고 가정 해 봅시다.

#include "a.h"

int i = 2;

int f() { i++; return i;}

파일 ah는 다음을 포함합니다 :

extern int i;
int f(void);

bc 파일에는 다음이 포함됩니다.

#include <stdio.h>
#include "a.h"

int main(void){
    printf("%d\n", f());
    return 0;
}

헤더의 extern은 링크 단계에서 컴파일러에게 "정의가 아니라 선언"이라고 알려주기 때문에 유용합니다. i를 정의하고 공간을 할당하고 값을 할당하는 ac 행을 제거하면 프로그램이 정의되지 않은 참조로 컴파일하지 않아야합니다. 이것은 개발자에게 변수를 참조했지만 아직 정의하지 않았다고 알려줍니다. 반면에 "extern"키워드를 생략하고 int i = 2행을 제거 하면 프로그램이 여전히 컴파일됩니다. 기본값은 0으로 정의됩니다.

파일 범위 변수는 함수 상단에 선언하는 블록 범위 변수와 달리 명시 적으로 값을 지정하지 않으면 기본값 0 또는 NULL로 암시 적으로 정의됩니다. extern 키워드는 이러한 암시 적 정의를 피하므로 실수를 피할 수 있습니다.

함수의 경우 함수 선언에서 키워드는 실제로 중복됩니다. 함수 선언에는 암시 적 정의가 없습니다.


int i = 2-3 번째 단락 에서 줄 을 삭제 하시겠습니까? 그리고 int i;컴파일러가 해당 변수에 메모리를 할당하지만 extern int i;, 컴파일러가 메모리를 할당하지 않고 변수를 다른 곳에서 검색 한다는 것을 알면 상태가 맞 습니까?
Frozen Flame

실제로 "extern"키워드를 생략하면 ac 및 bc에서 i가 재정의되어 (h로 인해) 프로그램이 컴파일되지 않습니다.
Nixt

15

extern키워드는 환경에 따라 다른 형태를 취한다. 선언을 사용할 수있는 경우 extern키워드는 번역 단위에서 앞서 지정한 연결을 사용합니다. 그러한 선언이 없으면 extern외부 연결을 지정합니다.

static int g();
extern int g(); /* g has internal linkage */

extern int j(); /* j has tentative external linkage */

extern int h();
static int h(); /* error */

다음은 C99 초안 (n1256)의 관련 단락입니다.

6.2.2 식별자의 연결

[...]

4 해당 식별자의 사전 선언이 보이는 범위에서 스토리지 등급 지정자 extern으로 선언 된 식별자 사전 선언에 지정된 연결로. 사전 선언이 표시되지 않거나 사전 선언이 연결을 지정하지 않으면 식별자에 외부 연결이 있습니다.

5 함수에 대한 식별자 선언에 스토리지 클래스 지정자가 없으면 해당 링크는 스토리지 클래스 지정자 extern으로 선언 된 것처럼 정확하게 결정됩니다. 오브젝트의 식별자 선언에 파일 범위가 있고 스토리지 클래스 지정자가없는 경우 링크는 외부입니다.


표준입니까, 아니면 일반적인 컴파일러의 동작을 말하고 있습니까? 표준의 경우 표준에 대한 링크가 기쁘다. 그러나 감사합니다!
Elazar Leibovich

이것이 표준 동작입니다. C99 초안은 < open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf >에서 확인할 수 있습니다 . 실제 표준은 자유롭지 않습니다 (초안은 대부분의 목적에 충분합니다).
dirkgently

1
방금 gcc에서 테스트했으며 "extern int h (); static int h () {return 0;}"및 "int h (); static int h () {return 0;}"모두 동일하게 허용됩니다. 경고. ANSI가 아닌 C99입니까? gcc에게는 사실이 아니기 때문에 초안의 정확한 섹션을 참조 할 수 있습니까?
Elazar Leibovich

다시 확인하십시오. gcc 4.0.1과 동일하게 시도했지만 어디에 있어야 오류가 발생합니다. 다른 컴파일러에 액세스 할 수없는 경우 comeau의 온라인 컴파일러 또는 codepad.org를 사용해보십시오. 표준을 읽으십시오.
dirkgently

2
@dirkgently, 내 진짜 질문은 함수 선언과 함께 exetrn을 사용하는 데 영향이 있으며, 왜 없으면 함수 선언에 extern을 추가 할 수 있습니까? 대답은 "아니오"입니다. 효과가 없으며, 표준이 아닌 컴파일러에서 효과가있었습니다.
Elazar Leibovich

11

인라인 함수에는 의미 에 대한 특별한 규칙 이 있습니다 extern. (인라인 함수는 C99 또는 GNU 확장이며 원래 C에는 없었습니다.

인라인이 아닌 기능의 경우 extern기본적으로 켜져 있으므로 필요하지 않습니다.

C ++의 규칙은 다릅니다. 예를 들어, extern "C"C ++에서 호출 할 C 함수의 C ++ 선언에 필요하며에 대해 다른 규칙이 inline있습니다.


이것은 정답이며 실제로 질문에 대답하는 유일한 대답입니다.
robinjam

4

IOW, extern은 중복 적이며 아무것도하지 않습니다.

이것이 10 년 후의 이유입니다.

참조 ad6dad0 커밋 , b199d71 커밋 , 5,545,442 커밋 에 의해 (2019년 4월 29일) 덴튼 리우 ( Denton-L) .
(가 합병 - Junio C 하마노 gitster-4aeeef3 커밋 2019 13 월)

*.[ch]:를 extern사용하여 함수 선언에서 제거spatch

extern함수 선언에서 제거하도록 푸시되었습니다 .

externCoccinelle에서 잡은 함수 선언에 대해 " " 의 일부 인스턴스를 제거하십시오 .
Coccinelle은 __attribute__varargs를 사용하여 함수를 처리하는 데 약간의 어려움이 있으므로 일부 extern패치는 향후 패치에서 처리되도록 남겨 두었습니다.

이것은 사용 된 Coccinelle 패치입니다.

  @@
    type T;
    identifier f;
    @@
    - extern
    T f(...);

그리고 그것은 다음과 같이 실행되었습니다 :

  $ git ls-files \*.{c,h} |
    grep -v ^compat/ |
    xargs spatch --sp-file contrib/coccinelle/noextern.cocci --in-place

그러나 이것이 항상 간단한 것은 아닙니다.

Denton Liu ( )의 commit 7027f50 (2019 년 9 월 04 일)을 참조하십시오 . (가 합병 - 덴튼 리우 -7027f50 커밋 2019 05 구월)Denton-L
Denton-L

compat/*.[ch]: externspatch를 사용하여 함수 선언에서 제거

5545442 ( *.[ch]: externspatch, 2019-04-29, Git v2.22.0-rc0을 사용하여 함수 선언에서 제거)에서 우리는를 사용하여 함수 선언에서 externs를 제거 spatch했지만 compat/일부는 업스트림에서 직접 복사되므로 파일을 의도적으로 제외했습니다. 향후 업데이트를 수동으로 병합하는 것이 더 간단 해집니다.

마지막 커밋에서는 업스트림에서 가져온 파일을 확인하여 파일을 제외하고 spatch나머지 파일에서 실행할 수 있습니다 .

이것은 사용 된 Coccinelle 패치입니다.

@@
type T;
identifier f;
@@
- extern
  T f(...);

그리고 그것은 다음과 같이 실행되었습니다 :

$ git ls-files compat/\*\*.{c,h} |
    xargs spatch --sp-file contrib/coccinelle/noextern.cocci --in-place
$ git checkout -- \
    compat/regex/ \
    compat/inet_ntop.c \
    compat/inet_pton.c \
    compat/nedmalloc/ \
    compat/obstack.{c,h} \
    compat/poll/

Coccinelle은 __attribute__varargs를 처리하는 데 문제가 있으므로 나머지 변경 사항이 남아 있지 않도록 다음을 실행했습니다.

$ git ls-files compat/\*\*.{c,h} |
    xargs sed -i'' -e 's/^\(\s*\)extern \([^(]*([^*]\)/\1\2/'
$ git checkout -- \
    compat/regex/ \
    compat/inet_ntop.c \
    compat/inet_pton.c \
    compat/nedmalloc/ \
    compat/obstack.{c,h} \
    compat/poll/

Git 2.24 (2019 년 4 분기)에서는 스퓨리어스 extern가 제거됩니다.

Emily Shaffer ( )의 commit 65904b8 (2019 년 9 월 30 일)을 참조하십시오 . 도움 : Jeff King ( ) . Denton Liu ( )의 commit 8464f94 (2019 년 9 월 21 일)를 참조하십시오 . 도움 : Jeff King ( ) . (가 합병 - Junio C 하마노 -59b19bc 커밋 2019 07 10 월)nasamuffin
peff
Denton-L
peff
gitster

promisor-remote.h: extern함수 선언에서 삭제

이 파일을 작성하는 동안 새로운 함수 선언이 도입 될 때마다 extern.
그러나 5545442 ( *.[ch]: 2019-04-29, Git v2.22.0-rc0을 extern사용하여 함수 선언에서 제거) 부터 시작 하여 spatchexterns가 불필요하기 때문에 함수 선언에 사용되지 않도록 적극적으로 노력하고 있습니다.

이 스퓨리어스를 제거하십시오 extern.


3

extern키워드 알리는 함수 나 변수가 외부 링크 가지고있는 컴파일러 - 그것이 정의 된 것 이외의 파일에서 보이는 것을, 다른 말로한다. 이런 의미에서 static키워드 와 반대의 의미를 갖습니다 . extern다른 파일에는 정의가 표시되지 않거나 여러 정의가 발생하기 때문에 정의 시점에 배치하는 것이 약간 이상합니다 . 일반적으로 extern외부 가시성 (예 : 헤더 파일)이있는 선언을 넣고 다른 곳에 정의를 넣습니다.


2

extern 함수를 선언한다는 것은 컴파일 시가 아니라 링크시 정의가 해석됨을 의미합니다.

extern으로 선언되지 않은 일반 함수와 달리 소스 파일 중 하나에서 정의 할 수 있지만 여러 소스 파일에서는 정의 할 수 없습니다. 따라서 링커는 동일한 파일에서 함수 정의를 확인합니다.

나는 이것이 그렇게 유용하다고 생각하지는 않지만 그러한 종류의 실험을 수행하면 언어의 컴파일러와 링커가 어떻게 작동하는지에 대한 더 나은 통찰력을 얻을 수 있습니다.


2
IOW, extern은 중복 적이며 아무것도하지 않습니다. 그렇게하면 훨씬 더 명확 해집니다.
Elazar Leibovich

@ElazarLeibovich 방금 코드베이스에서 비슷한 사례가 발생하여 같은 결론을 얻었습니다. 여기에 답변을 통해 모든 사람들이 하나의 라이너에 요약 될 수 있습니다. 실질적인 효과는 없지만 가독성이 좋을 수 있습니다. 만나서 반갑습니다 :)
Aviv

1

그것이 효과가없는 이유는 링크 타임에 링커가 extern 정의를 해결하려고하기 때문입니다 (귀하의 경우 extern int f()). 파일을 찾은 한 동일한 파일 또는 다른 파일에서 찾더라도 중요하지 않습니다.

이것이 귀하의 질문에 대답하기를 바랍니다.


1
그렇다면 왜 extern어떤 기능에도 추가 할 수 있습니까?
Elazar Leibovich

2
게시물에 관련없는 스팸을 두지 마십시오. 감사!
Mac
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.