[완료] extern 사용 공부하고 있는데, 어려운 것같습니다. 조금 살펴봐 주세요.

sia79의 이미지

제가 extern 을 공부한 내용은 아래와 같습니다.
extern 은 파일 외부의 변수를 참조하기 위한 키워드입니다.
extern 으로 선언을 하면 어딘가 다른 파일에서는 반드시 정의를 해야합니다.

스스로 예제를 만들어서 하는데,

a.h 에 extern char* haha; a.c파일도 존재합니다.
b.h 에 extern char* haha; b.c파일도 존재합니다.
a.h, b.h를 포함시키는 c.h 에 char* haha = "smile"; 로 정의했습니다. a.c파일도 존재합니다.
이제 main함수가 있는 main.c 안에 #include "c.h" 를 포함시키고, c.c 함수를 부르도록 했습니다.
obj파일은 전부 생성되는데. 링크에서 에러가 발생합니다.

../obj/c.o : In function ... ... : c.c : multiple definition of 'haha'
../obj/main.o : ... ... ... main.c : first defined here

라는 메세지가 나오더군요.

그래서, c.h에 정의한 것을 extern 선언으로 바꾸었고,
main.c에 haha를 전역변수로 정의를 했더니 에러 메세지가 안나옵니다.

extern의 정의는 꼭 main함수가 있는 곳에서 해야하나요?
책의 내용을 다시 봐도 어딘가 다른 파일에서는 반드시 한번은 정의해야한다고 읽혀집니다.
제가 뭔가 간과하고 있는 사항이 있다면 알려주시길 바랍니다.

jick의 이미지

원글에서 쓰신 것과 같이 했으면 main.c와 c.c 양쪽에서 모두 c.h를 include했으니 main.c에서도 char *haha를 한번 정의하고, c.c에서도 한번 정의한 것과 같습니다. 그러므로 중복 정의 에러가 나게 되죠.

변수 정의는 main 함수와 무관하게 아무 C 파일에서 해도 일반적으로 아무런 차이가 없습니다만, 방금 말한 것과 같은 이유로 인해 헤더 파일에서는 일반적으로 정의를 쓰지 않습니다. 혹시 운이 좋아서 되더라도 (= 헤더 파일이 단 한번만 include되는 경우) 나중에 그 헤더 파일을 다른 C 파일에서 또 include하면 다시 에러가 발생할 테니까요.

sia79의 이미지

말씀 감사합니다. 중복 정의에 대한 말씀으로, 각 헤더마다 아래를 추가해주었는데, 같은 에러가 나는군요.
#ifndef _헤더명_H
#define _헤더명_H
... 헤더 내용.
#endif

위 처럼 해주었다면 main.c 에서 한번 포함되었으니 중복 에러가 나지 않을꺼라고 생각했는데.
같은 에러가 나오는군요.;;;

글로벌 변수는 어딘가 따로 파일을 만들어 정의해두는 것이 좋은가요?

조금만 더 설명을 해주시면 감사하겠습니다.

appler의 이미지

전역 변수를 다른 파일에서 호출가능하게 사용하는게 extern으로 알고 있는데요

물로 변수외에도 함수도 가능하지만...

저 역시 헤깔리더군요.

나무를 생각하시고 그 가지로 뿌려준다는 생각으로 코드를 해보심이.

가지가 다른 가지를 제어하는건 아무래도 이상하지 않겠나요??

이상 입니다.
-----------------------------
탄생은 죽음의 시작에 불과하다.


laziness, impatience, hubris

不恥下問 - 진정으로 대화를 원하면 겸손하게 모르는 것은 모른다고 말하는 용기가 필요하다.

sia79의 이미지

말씀이 제겐 좀 어렵군요^^;;;

의견 감사합니다.

kalstein의 이미지

선언과 정의는 다릅니다.

정의를 하게되면 실제 인스턴스가 obj파일에 존재하게됩니다.

동일한 이름의 정의가 2번 반복되면 multiple definition Error 가 나지요 ^^


------------------------------------------
Let`s Smart Move!!
http://kalstein.tistory.com/

sia79의 이미지

아... 그럼, haha가
c.o 에 정의 된 것이 남고.
main.o 에 정의 된 것이 남아서.

obj를 묶을 때, 충돌이 생기는 것인가요? 조금 감이 오는 것같은데... 그런 것인가요? ^^;;

errno.h 의 errno 변수도 전역이라고 알고 있는데,

a.h b.h c.h 에 이 헤더는 아무리 추가 시켜도 에러가 안나는 것은 어떤 이유인가요?

전역 변수는 한번만 정의하기 위해 어떻게 관리 되는 것이 좋은지도 가르쳐주세요~.

cleol의 이미지

[인용]
아... 그럼, haha가
c.o 에 정의 된 것이 남고.
main.o 에 정의 된 것이 남아서.

obj를 묶을 때, 충돌이 생기는 것인가요? 조금 감이 오는 것같은데... 그런 것인가요? ^^;;
[/인용]

네 말씀하신대로 입니다. 그리고 다른 여러분들이 말씀해주셨듯이 해결방법은 "정의" 를 c.c 에서 하는 겁니다.

이미 다른 분들이 잘 설명해주셨지만 제가 다시 정리하면,

1. 기본적으로 "선언" 은 헤더파일에 "정의"는 .c 파일에 두어야한다고 생각하십시오.

2. 선언은 object (변수나 함수)의 형(type) 에 대해서 컴파일러에게 알려주는 겁니다. 어떤 변수나 함수를 "사용"하는 코드를 컴파일할 때에는 컴파일러가 변수의 값이나, 함수가 하는 일을 몰라도 됩니다. 하지만 적어도 자료형은 알아야만 합니다. 고로 헤더 파일에는 선언(자료형에 대한 정보)을 둬서, object 를 "사용"하는 코드가 그 헤더파일을 include 해서 컴파일될 수 있도록 하는 겁니다. 헤더 파일에는 어떤 object 를 사용하는 코드를 컴파일하는 데에 필요한 "최소한"의 정보만 담아둬야한다고 생각하십시오.

3. 변수 초기화나 함수의 내용 (즉, 정의) 은 당연히 전체 코드 중에 "한 번만" 나와야 합니다. 그렇지 않으면 컴파일러가 여러 정의 중 어떤 것을 사용해야할 지 정할 수 없을테니까요.

4. 전역 변수를 어디서 정의할 지는 딱히 기준이 없습니다. 좋은 습관은 가능한 한 전역 변수를 사용하지 않는 것입니다.

5. "extern 은 파일 외부의 변수를 참조하기 위한 키워드입니다." 라고 쓰셨는데 그렇지 않습니다. extern 은 어떤 object 를 module (컴파일 단위, 즉 하나의 .c 파일) 외부에서도 접근할 수 있도록 허용한다고 (즉, 링크할 수 있도록 허용한다고) 링커에게 알려주는 겁니다. 반대로 static 은 어떤 object(변수/함수) 를 module 외부에서는 접근할 수 없도록 한다는 뜻이구요. 해당 object 를 "사용" 하는 파일 입장에서는 extern 이건 static 이건 관계가 없습니다. 해당 object 를 "정의" 하는 파일에서 중요한 의미가 있는 것이지요. 변수는 기본적으로 static 입니다. 그런데 전역 변수는 모든 모듈에서 접근할 수 있어야하니까 extern 을 붙여서 그 변수를 정의하는 모듈이 아닌 다른 모듈에서도 접근할 수 있도록 하는 겁니다. 그리고 함수는 기본적으로 extern 이므로 따로 extern 키워드를 사용할 필요가 없습니다. 즉, 함수에 선언에 static 키워드를 사용하지 않는 이상 모든 모듈에서 그 함수를 사용할 수 있는겁니다.

sia79의 이미지

말씀 감사합니다. 상세한 설명으로 좀 더 감을 잡은 느낌입니다. 감사합니다.

vamf12의 이미지

Quote:
a.h, b.h를 포함시키는 c.h 에 char* haha = "smile";

이거 때문입니다. c.c에 haha를 정의해도 되지만, c.h에 정의 하면, 해당 정의를 인클루드 하는 모든 오브젝트에 haha변수가 생성될 겁니다.

결국 haha의 정의를 c.c로 옮기시면 간단하게 되겠네요.

sia79의 이미지

말씀 감사합니다.

ktd2004의 이미지

먼저 .c 파일 .h 파일, 그리고 전처리기에 대한 개념을 잡으셔야 할 것 같습니다.
.c : 정의. 실제 존재하는 변수와 함수들
.h : .c 파일에서 다른 .c 파일에서 이용할 수 있도록 한 변수와 함수들의 선언.

전처리기가 "#include"를 만나면, 해당 파일을 그 위치에 그대로 붙여넣기 해버립니다.

c.h 파일에 "char *haha = "smile";"이 있다면,

컴파일러는 c.h 파일을 main.c에서 #include 했다면,
전처리가 끝난 main.c 소스파일에는 "char *haha = "smile";"이 들어가게 됩니다.

이제 링커가 각 .o 파일들을 모을려고 보니까. "haha"라는 것이 main.o와 c.o에 모두 들어있다는
에러가 발생하는 것입니다.

.c 파일에
* 함수들과 전역변수을 사용해서 정의합니다.
* 외부(다른 .c 파일)에서 사용할 필요가 없는 함수들과 전역변수들은 static 으로 선언해줍니다.
* 외부에서 사용할 함수들이나 변수들은 "extern"으로 헤더파일에 선언합니다.

다른 .c 파일에서는 c.c 파일에 있는 전역변수나 함수를 사용하기 위해
* "#include "c.h"" 만 하면 됩니다.

sia79의 이미지

말씀 감사합니다.

아르아의 이미지

죄송합니다.
잘못 답글을 달았는데 지울수가 없네요.

cppig1995의 이미지

C 파일 하나는 컴파일 단위입니다.

sourcefilename (#includes)
==========================
a.c (a.h)
b.c (b.h)
main.c (a.h, b.h)

이런 관계의 다섯 파일이 있으면, 컴파일 단위는 3개입니다.

a.c 컴파일 단위 = a.h 내용 + a.c 내용
b.c 컴파일 단위 = b.h 내용 + b.c 내용
main.c 컴파일 단위 = a.h 내용 + b.h 내용 + main.c 내용

H 파일 자체는 컴파일되지 않습니다. 다만, 전처리기에 의해 C 파일 속에 #포함됩니다.
이제 시작해 봅시다.

b.c에 void logError(char *errorstr)라는 함수가 있고, char logfilename[100]이라는 배열이 있다고 합니다.
logError 함수는 logfilename 파일에다가 오류 로그를 출력하는데, 그래서 사용자가 logfilename을 변경할 수 있게
extern문을 써야 합니다. (왜 배열인 건지는 나중에 밝혀지니 그냥 읽어주세요.)

그러면 char logfilename[100];과 extern char logfilename[100];은 어디에 써줘야 할까요?
전자는 b.c에, 후자는 b.h에 써야 합니다. b.h의 내용 전체는 이런 것이 되겠죠.

#ifndef B_H_
#define B_H_
extern char logfilename[100];
void logError(char *errorstr);
#endif

(참고로 저는 VC++에서는 그냥 #pragma once를 쓰고, 이식성이 중요하면 프로그램명_파일명_H__UUID 식으로 UUID를 붙이는 편입니다.)

둘의 차이를 구분하실 수 있겠나요? logfilename[100]은 하나고, 이걸 char logfilename[100];으로 만들어줍니다.
그리고 extern char logfilename[100];은 "이 파일에서도 logfilename에 접근 좀 하겠소. char [100] 형이라고 생각해주시오."라는
부탁인 것입니다.

잠깐! 여기서 "b.h에서 extern 어쩌구를 썼으니 b.h에서만 logfilename에 접근할 수 있는 거 아닌가요?"
"b.h를 #포함한 main.c에서는 못 쓰죠!"라고 주장하신다면 위의 컴파일 단위에 대해 다시 읽어보세요.
b.h는 절대 컴파일되지 않습니다. 컴파일러는 b.h가 있는 줄도 모를 겁니다.

그런데 왜 배열인지는 밝혀진다고 했죠? C와 C++에서는 배열이 포인터로 둔갑할 때가 자주 있는데,
첫번째는 logfilename = &logfilename[0] (char *형)이고1), 두번째는 extern 할 때입니다.
컴파일러는 logfilename의 크기가 50인지 100인지 200인지, 심지어는 char 포인터인지도 상관하지 않습니다.
따라서 extern char logfilename[50];으로 쓰든 [200];으로 쓰든, [];으로 쓰든 *logfilename으로 쓰든 상관 없답니다.

extern은 또다른 사용법을 가지고 있는데, C++ 컴파일러로 컴파일된 함수는 C에서 못 쓰기 때문에(이름 부호화name mangling 비호환),
extern "C" 함수형 함수이름(매개변수리스트); 처럼 extern "C" 함수원형으로 써 주면 C와 호환되게 됩니다.
extern "C" { 함수1; 함수2; } 처럼 블럭으로도 쓸 수 있어요.
(사실, 직접 프로그램을 짤 때보다 다른 프로그램을 읽을 때 등장할 가능성이 더 높은 부분이었습니다.)

참고로 함수는 자동으로 extern이 붙는답니다.

조금이라도 도움이 되길 바랍니다!

1) 저도 잘못 알고 있었는데, 많은 분들이 logfilename = &logfilename = char *형이고 &logfilename = char **형이라더군요.
제가 터보 C 쓰던 사람이라(-_-) Suspicious 어쩌구 하던 터보 C 경고문(compiler warning)만 생각하고 있었답니다.



"그거 이름은 하늘이라고 합니다. 무사장님." - 제국군 가시나무 군단 312소대장 틸러 달비 부위
http://cppig1995.n-pure.net/mh || 몽화 대사전

Real programmers /* don't */ comment their code.
If it was hard to write, it should be /* hard to */ read.

jick의 이미지

extern char *x; extern char y[];

했으면 둘은 다르죠. 코드에서 x = NULL;은 되지만 y = NULL;은 안되지 않겠습니까?

cppig1995의 이미지

되지 않나요? 될 것 같은데요.

int main(int argc, char *argv[])
{
argv = 0;
}

이게 되면 extern에서도 될텐데. 이게 될 것 같지 않습니까?

사실 전 위에 배열이 포인터로 둔갑하는 예제에서 함수호출 빠뜨린거 지적해주신 줄 알고 뜨끔해했답니다.



"그거 이름은 하늘이라고 합니다. 무사장님." - 제국군 가시나무 군단 312소대장 틸러 달비 부위
http://cppig1995.n-pure.net/mh || 몽화 대사전

Real programmers /* don't */ comment their code.
If it was hard to write, it should be /* hard to */ read.

doldori의 이미지

argv와는 다른 경우죠.
다음 코드는 어떨까요?

// a.c
char str[10] = "hello";
 
void f(char* s)
{
    s[0] = 'j';
}
 
 
// b.c
extern char* str; // hmm... declaration differs from the definition
 
void f(char* s);
 
int main()
{
    f(str);
}

str을 배열이라고 했다가 포인터라고 했다가 하면서 컴파일러를 헷갈리게 만들면 컴파일러가 복수할 겁니다. ^^
doldori의 이미지

배열이 포인터로 변환되지 않는 세 가지 경우를 알아두는 것이 더 편할 겁니다.

* sizeof의 피연산자
* &(번지연산자)의 피연산자
* char 배열의 초기치로 쓰이는 string literal

extern과는 관계 없어요.

1) &logfilename의 형은 char**가 아니라 char(*)[100]입니다.

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.