Declaration은 identifier의 의미를 compiler에게 알려준다.
declaration-specifiers declarators ;
Dclaration은 위 구조를 가진다.
한 파트씩 차근차근 보자.
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
Declaration-specifiers는 선언되는 변수(or함수)의 특징을 알려준다. 이는 아래 네가지 카테고리로 나눠진다.
Storage classes
최대 하나의 storage class만 제일 앞에 올 수 있다.
Type qualifiers
0개 이상 포함할 수 있다.
Type specifiers
chapter 7에 설명했듯이 아무 순서로 와도 되고 조합될 수 있다. structure, union, enumeration도 여기에 포함된다. typedef로 만든 type도 여기에 포함된다.
Function specifier
Storage class만 제일 앞에 오고 나머지 Type specifier과 Type specifier의 순서는 상관없다.
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
Declarators는 변수(or함수)의 이름과 추가적인 정보를 알려준다.
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
함수든 변수든 위 규칙대로 Declare 할 수 있다.
variable의 storage class에 집중해서 우선 보자
if
, switch
, for
, do
, while
이런 애들도 전부 통째로 block으로 여겨진다. (자세한건 chapter 10에 내가 정리해둠. 책은 p.475 참고)
변수가 어디에 선언됐는지에 따라 변수에 대한 특징 세가지가 기본적으로 정해진다.
Storage class를 명시함으로써 위 세가지 특징을 바꿀 수 있다.
block안에서 선언된 variable에만 사용할 수 있다.
얘를 쓰면 automatic storage duration, block scope, no linkage가 된다.
그래서 전혀 쓸 필요가 없다. 왜냐하면 block안에서 선언됐으면 그냥 저 특징대로 되기 때문이다.
얘는 어디서 선언됐든 사용할 수 있다.
block 밖에서 선언된 변수에 static
을 적용하면
static storage duration
file scope
internal linkage
가 된다. (linkage 빼고는 그대로) (information hiding에 사용)
block 안에서 선언된 변수에 static
을 적용하면
static storage duration
block scope
no linkage
이 된다. (storage duration 빼고는 그대로)
static
변수가 가지는 특징
char digit_to_hex_char(int digit)
{
static const char hex_chars[16] = "0123456789ABCDEF";
//storage class는 제일 앞에 와야됨
return hex_chars[digit];
이렇게 매번 호출되는 함수의 변수를 static을 활용해 한번만 initialize되도록 할 수 있다. 이렇게 속도를 올릴 수 있다.
(static 없으면 호출될때마다 매번 hex_char가 생성돼 효율 떨어짐)
15.2에서 이미 본질적인 부분이 나왔다.
프로그램내에 variable의 declaration은 많아도 되지만 definition은 하나만 있어야 한다.
여러 파일에서 변수를 공유하기 위해 declare 만하려고 extern
을 사용한다. (메모리 할당 안됨)
extern int i = 0;
이렇게 extern
이 initializer와 함께 쓰이면 이는 함수의 definition이다. int i = 0;
얘와 같다.
extern
을 사용하면
static storage duration, (다른 파일과 공유하는게 주 목적인데 static이 아니면 의미가 없음)
scope는 위치에 따라 정해진다.
그리고 linkage는, file 내에서 static
으로 정의돼있으면 internal linkage고, 그게 아니면 external linkage이다.
(external이 디폴트고, static있으면 internal이 되는거임)
(다른 file에 static으로 정의돼있으면 그건 얘랑 이름같아도 그냥 다른 변수라서 생각할 필요없음. 같은 file 내에서 static으로 선언됐으면 얘도 그거에 맞춰서 internal linkage로 가는건 당연하다. 이 파일 내에서 static으로 선언된게 아니라면, 다른 파일과 공유돼야하니 위에서 static storage는 해주니까 external linkage까지 해서 다른 파일과 공유될 수 있게 맞춰주는거지) (stoarage class는 선언할 때 하나밖에 못오니까 static으로 선언됐다고 하는건 static int i;
이런식으로 돼있을 것이란 소리다. 여기다 storage class는 뭘 더 못붙임.)
이런 'extern' keyword는 헤더파일에서 변수 declare할때 사용
함수는 어차피 declaration만 헤더에 넣기때문에 extern 의미없고,
변수는 extern 없이 쓰면 declare가 아니라 definition이라 헤더에선 extern 해줘야됨.
헤더 안쓰고 그냥 같이 컴파일되는 다른 소스의 extern linkage 변수 참조할 수도 있는데,
이때도 그냥 extern으로 선언해주면 된다.
(17.8의 restricted pointer처럼 최적화 역할을 한다.)
resgister
storage class를 사용하면 해당 변수를 main memory가 아니라 register에 저장하도록 compiler에 요청한다.
(말 그대로 요청일 뿐 무조건 이뤄지는게 아니다.)
register : CPU 내의 저장공간이다. 다른 메모리에 비해 접근과 사용이 빠르다.
auto
처럼 block안의 variable에만 적용할 수 있다.
그래서 auto
와 같은 storage duration, scope, linkage를 가진다.
대신 register
는 주소가 없기 때문에 &
operator를 사용하는 것이 금지된다.
(compiler가 register에 저장하지 않고, 그냥 memory에 저장했더라도 이는 금지된다.)
자주 accessed되고, 자주 updated되는 변수에 적용하면 좋다.
예를들어 for statement의 control variable..
요즘엔 compiler가 알아서 뭐가 register에 들어가면 좋을지 잘 판단하기때문에 옛날만큼 많이 쓰이진 않는다. 그럼에도 적어주면 최적화 하는데 도움이 될 수 있다.
포인터로 접근/수정이 안된다는 점에서 retirct
와 어느 정도 연관이 있다.
extern
: 함수를 external linkage로 만들며 다른 file에서 호출할 수 있게 한다.static
: 함수를 internal likage로 만들며 함수가 정의된 file내에서만 사용할 수 있게한다. (function pointer를 이용한 간접적 호출은 허용된다.)둘중에서만 사용할 수 있다.
만약 함수에 표시된 storage class가 없다면, external linkage로 간주한다.
그래서 extern
을 적는건 사실상 변수에 auto
를 적는거나 마찬가지라서 안적어도 상관 없다.
반면에static
은 꽤 유용하다.
어떤 함수를 다른 파일과 공유할 목적이 없다면 static
으로 선언하는 것이 좋다.
Easier maintenance : static
으로 선언됐으면 해당 함수 내에서만 쓰였다는걸 쉽게 알 수 있어서 수정하기 쉽다. 다른 파일에서 함수 포인터로 해당 함수를 사용할 수도 있지만, 어차피 static 함수가 정의된 파일 내에서 넘겨줬을 것이니 알아보기 어렵지 않다.
Reduced "name space pollution" : 해당 함수 이름을 다른 함수에서 또 써도 OK. 일부러 같은 이름을 쓰진 않겠다만 큰 프로그램에선 그렇지 않기가 어렵다.
함수 parameter는 auto
variable과 같은 특징을 가진다.
(automatic storage duration, block scope, no linkage)
parameter에는 register
storage class만 명시될 수 있다.
int a;
extern int b;
static int c;
void f(int d, register int e)
{
auto int g;
int h;
static int i;
extern int j;
register int k;
}
Name | Storage Duration | Scope | Linkage |
---|---|---|---|
a | static | file | external |
b | static | file | ? |
c | static | file | internal |
d | automatic | block | none |
e | automatic | block | none |
g | automatic | block | none |
h | automatic | block | none |
i | static | block | none |
j | static | block | ? |
k | automatic | block | none |
여기서 '?'로 된 거는 딱 이 code만 놓고 보면 external linkage인데,
위에 static으로 선언된 같은 이름의 변수가 있다면 internal linkage이다.
아래 3개의 Type Qualifiers가 있다.
const
volatile
restrict
const
Type Qualifierconst int n = 10;
const int tax_brackets[] = {500, 1000, 1500};
"read-only" object를 선언하기 위해 사용된다.
access는 가능하지만, 수정은 안된다.
장점
1. code를 읽는 사람에게 해당 변수가 read-only인 것을 알려준다.
2. object 값을 수정하려는 것을 compiler가 check한다.
3. embedded system 같은 응용프로그램을 작성하려고 할때 data가 ROM(ReadOnlyMemory)에 저장된다고 표시할 수 있다.
#define
과의 차이?
1. #define
은 numerical, character, string 의 이름을 작성하는데 사용될 수 있지만,
const
는 array, pointer, structure, union 등등 아무 type이나 read-only object로 만들 수 있다.
2. const
는 해당 변수의 scope rule을 따른다.
3. const
object는 debugger에 보인다.
4. macro와 달리 const
object는 constant expression으로 쓰일 수 없다.
예를들어 array 의 크기에 const
object가 올 수 없다.(C99부터는 automatic storage duration이라면 VLA가 되기때문에 올수 있다.)
5. const
object는 주소가 있기 때문에 &
operator를 사용할 수 있다. macro는 물론 안된다.
상수 아닌걸로 initialize해도 OK
initializer에 상수가 와야되는건 storage duration의 문제이다.
declaration-specifiers declarators
에서 뒷부분인 declarators
에 대해 알아보자.
위에서 말했듯이 declarators
는
identifier
or plus *
or []
or ()
로 구성된다.
*
이 앞에 오면 pointer다.[]
가 뒤에 오면 array다.[]
는 비어있어도 된다.)*
이다.*
은 VLA argument임을 나타낼 때 사용할 수 있다.()
가 뒤에 오면 function이다.[]
and ()
over *
*
보단 []
랑 ()
가 먼저다. *
랑 []
가 같이있으면 그건 포인터가 아니라 배열을 나타낸다는 뜻 (물론 괄호를 이용해서 우선순위를 바꿀 수도 있다.)int *ap[10];
: array of pointer
float *fp(float)
: float * 을 return 하는 함수
float (*fp)(float)
: 괄호가 있어서 우선순위가 바뀌니까 얘는 포인터(float을 반환하고 float을 입력으로 받는 함수 포인터)
int *(*x[10])(void);
: int *을 반환하고 argument가 없는 함수를 가리키는 포인터의 배열
배열를 return 하는 함수,
함수를 return 하는 함수,
함수의 배열
은 선언할 수 없다.
int *(*x[10])(void);
typedef int *Fcn(void);
typedef Fcn *Fcn_ptr;
typedef Fcn_ptr Fcn_ptr_array[10];
Fcn_ptr_array x;
변수명 위치에 온걸로 typedef됨
이렇게 type으로 선언해두면 편하게 쓴다.
간단 예시
assignment에서의 =
와는 다르다.
즉, 아래 경우는 initialize할때를 말하는거지 assign 할때는 적용되지 않는다.
void *
이어야 한다.{}
로 이루어져있다.(designated initializer까지 해서..)static storage class 의문점
1. initialize할때 왜 꼭 constant expression이 와야하지?
: C99 5.1.2에 의하면 static storage duration은 프로그램 start up전에 initialize 돼있어야 한다. constant가 아니라 그냥 variable이면 값이 실행중에 결정되기 때문에 startup전에 initialize될 수 없다.(그래서 우리가 따로 초기화하지 않으면 아래 나와있듯이 0
으로 초기화 되는거다.)
2. 그럼 assign할때는 왜 constant expression이 아니어도 되지?
: 이미 startup전에 초기화 된 값을 수정만 하는건데 굳이 constant exrpession일 필요는 없다. 프로그램 실행중이니 variable이 와도 상관없다.
3. global variable을 함수 밖에서 assign하니까 오류가 뜬다 뭐지?? assign하는건 상관없다고 안했나???
: 함수밖에서는 assign하면 안된다.. 애초에 함수 밖에는 그냥 일반적인 exrpession code가 올 수 없다. 걔네가 함수 안에 있는 것도 아닌데 실행 순서를 알 수가 없다..
자세한 답변은 https://stackoverflow.com/questions/50661263/why-cant-i-assign-values-to-global-variables-outside-a-function-in-c
unitilized varaible의 값은 storage duration에 따라 결정된다.
zero
를 default 값으로 가진다. calloc이 모든 bits를 0으로 만드는 것과 다르게 실제 값을 0으로 만들어 준다. integer면 0, float이면 0.0, pointer면 null pointer로 만들어 준다.declaration-specifiers declarators
Declaration-specifiers 중 function specifier에 해당하는 inline
에 대해 알아보자.
inline
을 이해하기 위해 C compiler에서 함수가 호출되고 반환되는 machine instruction을 알아볼 필요가 있다.
함수를 호출하게 되면 함수 시작지점의 instruction으로 이동해서 실행하고 다시 돌아오게 된다.
(argument있으면 copy하고.. 등)
이런 함수의 기본적인 기능 외에 추가적인 작업을 overhead라고 하는데, 이걸 없애기위해 inline
을 사용한다.
(C89에선 parameterized macro 사용했는데, 결점이 있었음)
inline
을 사용하면 함수 호출 부분을 해당 함수의 machine instruction으로 compiler가 대체한다.
그래서 이걸 많이 사용하면 파일 크기가 커진다.
이것도 restrict
나 register
처럼 command가 아닌 suggestion에 불과하다.
inline int sum(int a, int b)
{
return a + b;
}
주의 : 이렇게 inline
으로 선언돼도 특별한게 없으면 external linkage이긴 하지만, external definition이 아니라 inline definition으로 보고, 다른 파일에서 이 함수를 사용하지 못한다.(error)
external definition이란? 그냥 함수 밖에서 선언된 함수 혹은 object(inline definition 제외)
이 external definition이 있어야 다른 파일에서도 함수 호출이 가능함.
대안
inline
함수는 아예 static
키워드를 포함시켜서 internal linkage임을 확실히 하자.
(C99에 보면 internal linkage면 external definition 포함하라고 돼있는데..? -> C99 다시 잘 보면 inline function과 inline definition을 구분해서 말하는 것 같음, 그렇게 구분해서 보면, static 포함한 internal linkage는 inline definition이 아니라서 상관없는 것 같은데.. 잘 모르겠네 뭐 집착할 필욘 없지싶다.)
inline
을 포함하지 않는 external definition을 다른 파일에 포함시키는 방법도 있다.(이러면 inline함수 정의가 없는 다른 파일들은 inlining되는게 아니라 그냥 함수호출하는 방식이겠지?) legal이긴 하지만, 두 버전이 일관되는지 보장하기 어려워 좋은 아이디어는 아니다.
header file에 inline
definition을 포함시킨다. 그리고 해당 함수가 필요한 파일에서 include 한다.
그리고 extern
keyword를 활용해서 external definition을 만든 후, 그 external definition이 있는 source file에 header file을 include해서 두 버전이 일관성을 가지도록 한다.
(자세한 구현은 p.474)
inline 공부하면서 들었던 의문점들 이 문서 최하단에 나름 정리해둠
일반적인 함수와는 실행되는 방식이 달라서 몇가지 제한 사항이 있다.
external linkage인 inline 함수는 아래 rule을 따른다.(internal linkage면 상관없음)
static
variable.수정가능한 static
은 안되니까, static
과 const
를 사용해서 변수를 선언하면 OK
(그러나 인라인 정의 각각이 그 변수의 복사본을 생성할 수 있음) (보통 함수 하나 정의해서 static const 쓰면 그 변수가 가리키는 하나의 object만 컨트롤 하는데, inline의 경우 함수마다 변수가 따로 놀 수도 있다는 뜻인듯)
gcc 같은 몇몇 컴파일러들은 inline
이 표준에 있기 전부터 지원해왔다.
그래서 사용법이 위에서 설명한 것(표준)과 좀 다를 수 있다.
자세한 설명은 p.475 참고...
selection statement, iteration statement가 block인 이유?
책엔 p.475에 있고, 블로그엔 chapter 10에 정리해둠. 간단하게만 말하자면, compound literal의 storage duration 일관성을 유지하기 위해 selection, iteration statement 전체를 block으로 봄.
automatic storage duration이면 block이 시작될 때 memory가 할당된다고 했는데, VLA도 마찬가지 인가?
No, block 시작할 때는 length를 모르기 때문에 할당할 수 없다. VLA declaration에 도달했을 때 할당된다. 이런 관점에서 볼때 VLA는 다른 automatic storage duration과 다르다.
scope와 linkage는 정확히 어떤 차이?
본문 linkage 설명에 걸린 stackoverflow 답변에서도 그 차이를 볼 수 있는데(scope는 그 변수가 어디까지 보이냐.. linkage는 두 변수가 같은 이름을 가지면 linkage 범위에 따라서 같은 object를 나타낼 것이냐..),
하나 더 설명하자면..
scope는 compiler의 편의를 위한거고, linkage는 linker의 편의를 위한 것이다. scope를 통해 compiler는 file 내에서 해당 변수가 사용될 수 있는지 판단한다.
그렇게 compiler가 source file을 object code로 만들고,,,
만들때 external linkage들을 표시함으로써 linker에게 internal linkage와 no linkage는 보이지 않고 external linkage만 보이게 된다.
(그럼 이 no linkage와 internal linkage의 차이는? -> 링크)
왜 const
object는 constant expression에 쓰일 수 없나? const
의 의미가 constant 아닌가?
C에서 const
의 의미는 constant가 아니라 "read-only이다.
const
object를 constant expression에 사용하도록 허용하면 생기는 문제 예시를 들어보겠다.
1. const
object는 그 lifetime 동안만 constant이다.
void f(int n)
{
const int m = n;
switch(...) {
case m : ... //WRONG
}
}
m은 함수가 실행될때까지 알 수 없다. 근데 constant expression이어야 하는 switch문의 label이 저런 식으로 선언되면 안된다.
2. 함수 밖에서 선언돼, 그 lifetime이 길어져도 문제는 여전히 남는다.
extern const int n;
int a[n]; //WRONG
n이 다른 파일에 정의돼있는 변수라면, 컴파일러가 a의 길이를 정의하는 것은 불가능하다.
(a는 (external variable일테니) static storage duration을 가지는데 그럼 a는 VLA도 될 수 없다.(p.175))
3. extern const volatile int real_time_clock;
심지어 volatile
을 같이 사용하면 프로그램 실행 중에 값이 바뀔수도 있다.(const
로 선언됐으므로 프로그램에 의해 바뀌는게 아니라, 다른 메커니즘에 의해 바뀔 수 있음)
declarator의 문법이 이상해보임
declarator는 그 사용을 흉내내려는 의도로 만들어졌다.
int *p;
같은 포인터 선언은 *p
라는 indirection operator를 적용하는 것과 비슷하고,
int a[~] = {~};
의 a[~]
는 array subscripting과 비슷하다. 함수도 마찬가지로 선언과 호출이 비슷하게(혹은 같게) 생겼다.
좀 복잡한 형태로 돼있는 declarator도 선언할때와 사용할때의 *
, parantheses, brackets 위치가 같다.
inline 함수 공부할때 궁금했던 점...
일단 external definition
이라는 단어를 여기서 처음 봤다.
그게 뭔지 C99 문서에 찾아보니, 대충 "함수 밖에서 declare되고 define된 함수나 object"라는데 중요한건 괄호에 "inline definition
은 제외"라고 적혀있다.(1번)
그리고 (2번)저 말이 적혀있는 같은 장에 "internal linkage로 함수 선언해서 사용하려면 external definition
이 있어야된다"고 한다.
근데 (3번)knk에선 inline
만 external linkage
로 쓰면 오류 생기니까 그 대안으로 static
을 쓰란다..(위에 보면 대안으로 나옴)
아니 그럼 여기서 궁금한게... 1번, 2번, 3번을 합쳐서 보면,
3번에서 inline
에 static
을 붙이면 internal linkage가 된다.
여기서 2번을 적용하면 해당 함수는 internal linkage니까 external definition
이 있어야 된다.
근데 1번을 보면 inline definition
은 external definition
이 아니란다...
그러면 knk에서 static
을 적용하는게 대안이라고 말할때,
external definition
을 선언해야 되지않나?
라고 생각하게 되었다.
그래서 stackoverflow고 뭐고 다 뒤져봤는데 명확한 답변이 없었다. 내가 너무 쓸데없는걸 생각하나? 싶기도한데 성격이 이런데 어쩌겠누..ㅋㅋ
고민하고 고민하다가 C99문서를 다시 자세히 읽어보니..
inline definition
과 inline function
을 구분해서 말하는 듯 했다.
inline definition
은 external linkage에서 선언한 경우에만 해당되는 것이다.(정확한진 모르겠다..)
나름대로 둘이 구분된다고 생각하고 C99에 나온 용어 그대로 정리를 해봤다..
inline
specifier로 선언된 함수를 inline function
이라고 함
inline function
으로 만드는건 함수 호출을 최대한 빨리 하도록 제안하는 것
아무 internal linkage인 함수는 inline function
이 될 수있다.
하지만 "external linkage"라면 몇몇 제한사항이 적용된다.(아래는 그럼 다 external linkage인 경우네..)
같은 UT에서 또 정의될 수 있다.
파일 내에 전부 다 extern
키워드 없이 inline
만 쓰면 이를 inline defintion
이라고 한다.(이거도 external linkage인 경우라고 보면 맞네, 위에서 external linkage의 제한사항 이라고 했으니까..?)
이 inline definition
은 external definition
을 제공하지 않는다.
그리고 다른 UT에 external definition
이 정의되는 것을 막지도 않는다.
이 inline defnition
과 external defition
중에 뭘 쓸지는 모른다. (external definition 필요한 이유 1)
C99 6.9 5에 external linkage가 exrpession에 쓰이려면 external definition
필요하다고 한다. (external definition 필요한 이유 2)
external linkage인 inline definition
에서 어떻게 external definition
을 만드나..? -> extern
키워드 활용 ㅎㅎ
이게 맞는진 모르겠는데 두 용어를 다르게 보고 해석하면 대충 들어맞는 것 같다.
결국 knk에서 오류가 뜨는 이유는 external likage에서 inline으로 선언해서 inline definition
이 있었기 때문이고,
이 대안으로
1. internal linkage로 만들어버리거나(이러면 inline definition 아님, 그리고 위에서 설명했듯이 다른 파일에서 호출 불가)
2. 그냥 external definition을 만드는 방법이 있다.
근데 2번 방법은 2가지 방법이 있어서 knk에서 따로 설명한 것 같다.