C 언어 - "Hello World!"로 시작하기
- 공유 링크 만들기
- X
- 이메일
- 기타 앱
1. C 컴파일러 설치
Linux 환경에서 C 언어 프로그램을 개발하기 위해 가장 널리 사용하는 컴파일러는 GCC(GNU Compiler Collection)이다. 이를 개별적으로 설치할 수도 있지만 개발에 필요한 필수 도구 모음인 build-essential 패키지를 통해 설치하는 것이 효율적이다.
$ sudo apt update
$ sudo apt install -y build-essential
$ gcc --version
gcc ...
$ make --version
GNU Make ...
- GCC (GNU Compiler Collection): 리눅스 환경의 표준 C 컴파일러이다.
- build-essential: GCC를 포함하여 g++(C++ 컴파일러), make(빌드 자동화 도구), libc6-dev(표준 라이브러리 및 헤더 파일) 등 개발에 필수적인 소프트웨어들을 한 번에 설치해 주는 패키지이다.
2. 첫 번째 프로그램
1978년에 발행된 교재의 첫 번째 예제로 “Hello, world!” 문장을 출력하는 예제가 소개된 이후 , 이 문장은 거의 모든 프로그래밍 언어 The C Programming Language [1] 를 설명하는 교재와 서적에서 가장 기본적인 첫 예제로 사용되고 있다.
2.1. hello.c
먼저 "hello.c" 라는 파일을 생성하고, “Hello, world!” 문장을 출력하는 예제를 작성한다.
hello.c
#include <stdio.h>
main() {
printf("Hello, world!\n");
}
"hello.c" 파일을 컴파일한다.
$ gcc hello.c
hello.c:3:1: warning: return type defaults to ‘int’ [-Wimplicit-int]
3 \
main() \
^~~~
위와 같이 경고(warning)가 발생하는 것을 확인할 수 있다. 해당 경고는 main() 함수에 반환 형식을 지정하지 않았기 때문에 발생하는 경고이다.
※ 이전 K&R 스타일에서는 반환형을 생략하면 int 로 가정하는 규칙이 있었으나, C99 이후 표준에서는 반드시 명시해야 하며 [2], 생략 시 컴파일러가 경고를 출력한다. C 언어에서 모든 함수는 어떤 값을 리턴하는지 지정하여야 한다.
hello.c 코드를 수정한다.
#include <stdio.h>
int main() {
printf("Hello, world!\n");
return 0;
}
"hello.c" 파일 수정후 다시 컴파일한다.
$ gcc hello.c
$ ./a.out
Hello, world!
"a.out" 실행파일이 생성되었고, 실행하면 "Hello, world!" 문장이 출력된다.
2.2. Makefile
생성할 파일이름 "hello"를 지정하여 컴파일한다.
$ gcc -o hello hello.c
$ ./hello
Hello, world!
이렇게 생성할 파일 이름을 gcc 실행 인자로 지정할 수 있다. 생성 파일 이외에서도 다양한 컴파일 옵션을 지정할 수 있고, 또 파일이 수정될 때만 컴파일이 수행되도록 할 수 있는데, 이것은 Makefile을 통하여 이루어진다. Makefile을 사용하면 소스 파일 간의 의존성을 정의하고, 필요한 경우에만 빌드를 수행하도록 자동화할 수 있다.
"Makefile"이라는 이름으로 다음과 같이 스크립트를 작성한다.
Makefile
TARGET = hello
all: $(TARGET)
$(TARGET): hello.c
gcc -o $(TARGET) hello.c
clean:
rm -f $(TARGET)
이제 다음과 같이 make 명령을 이용하여 컴파일 할 수 있다.
$ make
gcc -o hello hello.c
$ make clean
rm -f hello
3. 실행 파일 분석
컴파일된 실행 파일은 코드와 다양한 섹션으로 구성된 구조를 가지고 있다. 이를 분석해 보면 프로그램이 어떻게 동작하는지 이해할 수 있다.
3.1. .rdata (.rodata)
.rdata(ReadOnly Data) 또는 .rodata 섹션은 읽기 전용 데이터가 저장되는 영역으로, 변경되지 않는 상수 데이터가 배치된다.
$ objdump -s -j .rodata hello
Contents of section .rodata:
2000 01000200 48656c6c 6f2c2077 6f726c64 ....Hello, world
2010 2100
"Hello, world!" 문자열이 실행 파일 내부의 읽기 전용 영역에 저장되어 있는 것을 확인할 수 있다.
3.2. disassemble
실행 파일을 디스어셈블(disassemble)하면 실제 CPU가 실행하는 어셈블리 코드를 확인할 수 있다.
$ objdump -d -M intel --disassemble=main hello
0000000000001149 <main>:
1149: f3 0f 1e fa endbr64
114d: 55 push rbp
114e: 48 89 e5 mov rbp,rsp
1151: 48 8d 05 ac 0e 00 00 lea rax,[rip+0xeac] # 0x2004 <_IO_stdin_used+0x4>
1158: 48 89 c7 mov rdi,rax
115b: e8 f0 fe ff ff call 1050 <puts@plt>
1160: b8 00 00 00 00 mov eax,0x0
1165: 5d pop rbp
1166: c3 ret
또한 다음 명령을 통해 특정 심볼의 주소를 확인할 수 있다.
$ nm hello | grep _IO_stdin_used
0000000000002000 R _IO_stdin_used
∴ _IO_stdin_used = 0x2000 + 0x04 = 0x2004 → 48656c6c... = Hell...
_IO_stdin_used 주소를 기반으로 문자열이 저장되어 있는 위치를 계산하면, "Hello, world!" 문자열의 주소임을 알 수 있다.
※ GCC 최적화: printf 함수에 포맷 지정자가 없는 경우, 컴파일러가 자동으로 puts() 함수로 대체하여 더 효율적인 코드로 변환한다.
- 공유 링크 만들기
- X
- 이메일
- 기타 앱