1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | #include <stdio.h> double arr1[3]; int a = (int)"Hello"; int b = (int)arr1; int c = (int)&a; int main(void) { /* static double arr2[3]; static int i = (int)"World"; static int j = (int)arr2; static int k = (int)&j; */ printf("%d %d %d\n", a, b, c); return 0; } | cs |
1) static 기억부류를 갖는 '모든' 대상체는 프로그램 시작과 동시에(main 함수 호출 이전에) 메모리에 할당되는게 맞나요? 그래서 초기값은 상수식이어야 하나요?
2) 전역변수 a, b, c의 초기값으로 놓인 것들이 어떤 원리로 가능한 것인가요? 초기값이 상수식으로 취급 되는듯 싶어서 상수식만 올 수 있는 문맥에 위치시켜 보았더니 상수식은 아니라서 더욱 궁금하네요..
double arr1[3];
int a = (int)"Hello";
int b = (int)arr1;
int c = (int)&a;
정상적으로 컴파일되는 코드도 아니고 컴파일 에러가 발생하는 코드입니다.
해당 내용은 컴파일러 이론서를 참고하셔야 합니다. 미니 C 컴파일러를 제작하는 예제가 실린 책도 많습니다.
전역 변수, static 변수 등 초기화되는 데이터는 data 세그먼트에 들어갑니다.
그외 초기화되지 않는 데이터는 BSS 세그먼트에 들어갑니다.(그러나 이는 구현체에 따라 달라집니다)
https://en.wikipedia.org/wiki/.bss
https://en.wikipedia.org/wiki/Data_segment
두 항목을 참조하세요.
오픈 소스 표준 C 컴파일러 프로젝트인 beluga를 참고하세요.
https://github.com/mycoboco/beluga
더 전문적으로는 gcc나 clang 오픈 소스 프로젝트를 참고하세요.
실행 파일의 로딩 과정은 loader의 역할이며 <Linkers and Loaders>에 자세히 설명되어 있습니다.
https://en.wikipedia.org/wiki/Linker_(computing)
https://en.wikipedia.org/wiki/Loader_(computing)
두 항목을 참고하세요.
다음의 내용을 읽어보면 C 표준이 지원하는 문법인 것 같습니다. 물론 이를 컴파일러가 지원하냐 안하냐 차이는 있겠다만, 이것이 어떤 원리로 가능한지 궁금합니다. 그냥 막연히 가능하다고만 이해하고 넘어가면 좋겠지만.. 볼일보고 안닦은 기분이 들어서요..
"All the expressions in an initializer for an object that has static
storage duration shall be constant expressions or string literals." ― C99
6.7.8/4
"[An address constant] shall be created [...] or implicitly by the use
of an expression of array or function type." ― C99 6.6/9
C 언어 문법과는 관계가 없습니다. 전부 컴파일러 이론입니다.
<컴파일러 입문>(정익사) 같은 책을 보셔야 합니다.
표준안은 기준을 정할 뿐이고 정확한 동작 원리라는 건 없습니다. 정확한 동작은 전부 컴파일러 이론입니다.
https://en.wikipedia.org/wiki/Loader_(computing)
In Unix, the loader is the handler for the system call execve()
.[1] The Unix loader's tasks include:
- validation (permissions, memory requirements etc.);
- copying the program image from the disk into main memory;
- copying the command-line arguments on the stack;
- initializing registers (e.g., the stack pointer);
- jumping to the program entry point (
_start
).
프로그램을 실행하면 프로그램을 메모리에 로딩합니다. 이게 로더입니다.
전역 변수는 어떻게 할까? 로더가. 지역 변수는 어떻게 할까? 로더가. static은 어떻게 할까? 로더가. 그러면 얘네들의 배치는 어떻게 했지? 컴파일러가. 컴파일러가 배치한 건 어떻게 알지? 컴파일러마다 다름. 컴파일러가 다른 건? implementation dependent임. 구현체마다 다른 건 어떻게 알아? VC++이면 소스 코드 비공개이니까 undocumented 문서가 있거나 역어셈블해서 알아야지. GCC면 소스 코드 공개니까 GCC 소스 코드를 보며 공부해야 겠지. GCC 소스 코드는 못 보는데? 컴파일러 이론을 알아야지.
이렇게 됩니다.
표준안은 이렇게 해야 한다는 기준을 정했고, 동작 원리는 컴파일러 이론과 이를 구현한 컴파일러 제작자의 몫입니다.
미니 C 컴파일러를 구현하니 직접 컴파일러도 구현하며 동작 원리를 이해하게 되리라 생각합니다.
Memory management in C: The heap and the stack
http://www.inf.udec.cl/~leo/teoX.pdf
세그먼트에 대한 내용입니다. 어느 정도 참고가 되길 바랍니다.
도움주셔서 감사합니다. 남겨주신 자료는 즐겨찾기 추가해두고 천천히 읽어보도록 할게요. 지금 당장 모든 내용을 소화할 수 있는 실력은 아니라서요..
그런데 집필하신 책 1182쪽에 "전역 변수와 정적 변수는 컴파일 시점(compile-time)에 주소가 알려져 있고 고정되어 있어서..." 라는 문장을 고려해보면 다음의 코드는 문제도 없어야 하고 이해도 잘 됩니다.
#include <stdio.h>
int arr[3];
int a=(int)"Hello";
int b=(int)arr;
int c=(int)&a;
int main(void)
{
printf("%d %d %d\n", a, b, c);
return 0;
}
하지만 변수 a, b, c의 초기값으로 사용된 것들이 컴파일 시점에 알려져 있다면, 상수만 올 수 있는 문맥(case 레이블, 가변 길이 배열이 아닌 길이 지정 등)에도 쓸 수 있어야 한다고 생각했는데, 안되더라구요.
이와 관련해서 상수 수식(constant expression)이라는 것을 검색해 보았고, 컴파일하는 동안 결정되는 값을 가진 수식으로 설명되는데, 초기값으로 사용된 것들도 컴파일 시점에 주소가 알려져 있으니 상수 수식이 되어야 옳지 않나요? 이것 역시 C언어와 별개로 컴파일러 책을 통해서 해결해야 되는 문제인지 조심스럽네요..
우리는 main 함수가 호출되어야 프로그램이 '시작'된다고 말합니다. 즉 main 함수가 호출되어야 비로소 런타임이고, 그렇지 않으면 컴파일 타임인 것입니다. 따라서 a, b, c의 초기값으로 사용된 것들은 컴파일 타임에 충분히 계산될 수 있으므로 적법한 코드라고 생각됩니다. 하지만 이들이 상수로 취급(인정)받지 못하는 이유는 '번역'중에 평가될 수 없기 때문이라고 생각합니다.
cc.c:3:7: warning: cast from pointer to integer of different size [-Wpointer-to-int-cast]
cc.c:3:1: error: initializer element is not constant
cc.c:4:7: warning: cast from pointer to integer of different size [-Wpointer-to-int-cast]
cc.c:4:1: error: initializer element is not constant
cc.c:5:7: warning: cast from pointer to integer of different size [-Wpointer-to-int-cast]
cc.c:5:1: error: initializer element is not constant
컴파일되지 않는 부분을 무시하고, 전역 변수에 대해 답변한 것이 문제군요.
int를 long으로 바꿔주세요.
cast from pointer to integer of different size [-Wpointer-to-int-cast]경고를 잘 보면 이해가 될 것입니다.
크기가 다르니 되도 않는 변환이 됩니다.
주소 크기가 다르니 계산하게 됩니다. 계산하면 상수가 아니니 에러가 발생합니다.
#include <stdio.h>
int arr[3];
long a=(long)"Hello";
long b=(long)arr;
long c=(long)&a;
int main(void)
{
printf("%ld %ld %ld\n", a, b, c);
return 0;
}
이렇게 고치면 잘 됩니다.
전역 공간에서 계산이 발생하게 하면 동적이 되니 안 됩니다.(no computable, no dynamic)
전역 변수가 스택에 접근하게 한다면 이것 역시 computable이고, dynamic이 되므로 illegal입니다.(스택은 런타임 시에 동적으로 정해지므로)
저는 이 책의 저자가 아닙니다. 관리자입니다.
이들이 상수로 취급(인정)받지 못하는 이유는 '번역'중에 평가될 수 없기 때문이라고 생각합니다.
이 부분을 부연하자면, 스택에 할당되거나 어떤 계산이 발생하면 상수식으로 표현할 수 없습니다. 그래서 안 됩니다.
전역 변수의 주소가 고정되어 있다는 표현도 부연하자면,
컴파일 했을 때 전역 변수의 주소는 고정되어 있습니다. 그러나 이게 실행할 때 전역 변수의 주소를 미리 알고 있다는 의미는 아닙니다. 로딩할 때 주소를 재배치하는 과정이 일어납니다. 이게 로더의 역할이고, 주소를 재배치하는 과정을 리로케이팅(relocating)이라고 합니다.
https://en.wikipedia.org/wiki/Relocation_(computing)
프로그램 실행은 로더의 역할이 끝난 다음입니다.
그러나 한 번 로딩되어 메모리에 적재된 상태에서는 주소가 고정되어 있습니다.
long으로 고쳐서 테스트한 환경은 64비트 리눅스이고, 64비트 윈도는 또 다릅니다. 64비트 윈도라면 long long으로 고쳐야 합니다. 이유는 각 플랫폼에 따라 데이터 모델이 다르기 때문입니다.
85.3 데이터 모델
https://dojang.io/mod/page/view.php?id=737
데이터 모델 참고해서 자신의 환경에 맞게 고쳐야 합니다.