리눅스 환경에서 C 언어를 개발할 때 자주 사용하는 도구는 GCC와 gdb이다.
이 글에서는 GCC 컴파일러와 gdb 디버거의 개념과 사용법을 초보자도 이해할 수 있도록 친절하게 정리하였다.
리눅스는 우리가 gcc와 gdb 명령어를 실행할 수 있도록 도와주는 운영체제이다.
터미널에서 gcc
나 gdb
를 입력하면, 리눅스는 시스템 내부에 설치된 해당 프로그램을 찾아 실행시킨다.
gcc
는 C 코드를 실행 파일로 변환해주는 프로그램gdb
는 실행 파일을 분석하고 디버깅할 수 있도록 해주는 도구즉, gcc와 gdb는 리눅스 위에서 작동하는 프로그램이며,
리눅스 터미널은 이 도구들을 사용하기 위한 창구 역할을 한다.
리눅스를 깊이 몰라도 gcc와 gdb를 사용할 수 있지만,
리눅스를 잘 이해하면 문제 해결이나 자동화, 디버깅에 훨씬 유리하다.
GCC는 GNU Compiler Collection의 약자로, C, C++, Objective-C 등 여러 언어를 컴파일할 수 있는 도구이다.
우리가 작성한 .c
파일을 기계가 이해할 수 있는 실행 파일로 바꿔주는 역할을 한다.
리눅스에서는 가장 기본이 되는 컴파일러이며, 대부분 기본으로 설치되어 있다.
GCC가 설치되어 있는지 확인하고, 없다면 아래 명령어로 설치할 수 있다.
sudo apt update
sudo apt install gcc
설치 후 버전이 제대로 나오는지 확인한다.
gcc --version
버전 정보가 출력되면 설치가 완료된 것이다.
gcc main.c -o main
./main
gcc main.c
는 main.c
파일을 컴파일하라는 뜻이다.-o main
은 결과물로 나올 실행 파일 이름을 main
으로 지정하겠다는 뜻이다../main
은 만들어진 실행 파일을 실행하는 명령이다.옵션 | 설명 |
---|---|
-o | 실행 파일 이름 지정 |
-Wall | 코드에 문제가 있을 때 경고를 모두 표시 |
-g | gdb에서 사용할 수 있도록 디버깅 정보 포함 |
-O2 | 프로그램 실행 성능을 위한 최적화 |
-std=c99 | C언어의 C99 표준을 따르도록 설정 |
예시:
gcc -Wall -g -std=c99 main.c -o main
-S
, -Og
)C 코드를 어셈블리어로 보고 싶을 때는 -S
옵션을 사용한다.
gcc -S -Og main.c
-S
: 실행파일 대신 .s
어셈블리 파일 생성-Og
: 디버깅에 적절한 기본 최적화 적용 (학습에 유용)실행파일에서 어셈블리를 추출하고 싶다면 objdump
를 사용한다.
gcc -g main.c -o main
objdump -d main
-g
: 디버깅 정보 포함objdump -d
: 실행파일 내부의 기계어를 역어셈블해서 출력구조체의 메모리 정렬(Padding)을 확인할 수 있는 간단한 실험 코드이다.
이 코드를 통해 컴파일러가 구조체 멤버 사이에 어떻게 빈 공간을 넣는지 확인할 수 있다.
#include <stdio.h>
typedef struct {
char a;
int b;
char c;
} MyStruct;
int main() {
MyStruct s = {'A', 42, 'B'};
printf("sizeof(MyStruct): %zu\n", sizeof(MyStruct));
unsigned char *p = (unsigned char *)&s;
for (size_t i = 0; i < sizeof(MyStruct); ++i) {
printf("Byte %zu: 0x%02X\n", i, p[i]);
}
}
gcc -g struct.c -o struct
./struct
gdb는 GNU Debugger의 약자로, C 프로그램을 디버깅할 수 있게 해주는 도구이다.
프로그램이 실행될 때 변수 값이 어떻게 바뀌는지, 어디에서 문제가 발생하는지 등을 추적할 수 있다.
예를 들어 코드가 잘못되어 값이 이상하게 나오거나, 중간에 멈춰버리는 경우 gdb
를 사용하면 그 원인을 파악할 수 있다.
gdb가 설치되어 있지 않다면 아래 명령어로 설치할 수 있다.
sudo apt install gdb
gdb는 프로그램 내부 정보를 필요로 하기 때문에 -g
옵션을 붙여 컴파일해야 한다.
gcc -g main.c -o main
gdb ./main
명령어 | 설명 |
---|---|
break main | main() 함수에 중단점을 설정 |
run | 프로그램 실행 |
next 또는 n | 한 줄씩 실행 (함수 내부는 들어가지 않음) |
step 또는 s | 한 줄씩 실행 (함수 내부로 들어감) |
print 변수명 | 변수 값 확인 |
x/s 포인터 | 문자열(포인터) 출력 |
continue | 다음 중단점까지 실행 |
quit | gdb 종료 |
아래 코드는 구조체 배열을 동적 할당하고, gdb로 중간 상태를 확인할 수 있게 해주는 예제이다.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
int id;
char name[20];
} User;
int main() {
int n = 3;
User *users = malloc(sizeof(User) * n);
for (int i = 0; i < n; i++) {
users[i].id = i + 1;
sprintf(users[i].name, "user%d", i + 1);
}
for (int i = 0; i < n; i++) {
printf("[%d] %s\n", users[i].id, users[i].name);
}
free(users);
return 0;
}
컴파일과 실행:
gcc -g user.c -o user
gdb ./user
gdb 실행 후 흐름 예시:
(gdb) break main
(gdb) run
(gdb) next
(gdb) print users
(gdb) print users[1].name
(gdb) x/s users[1].name
C 개발을 하다 보면 꼭 만나게 되는 기능이나 개념들을 정리하였다.
처음에는 몰라도 되지만, 프로젝트가 커지면 반드시 필요해지는 내용이다.
-Wextra
, -Werror
)gcc -Wall -Wextra -Werror main.c -o main
-Wextra
: 더 많은 경고 출력-Werror
: 경고도 컴파일 에러로 처리 (수정 강제)./main < input.txt > output.txt
input.txt
: 표준 입력을 대신할 파일output.txt
: 출력 결과가 저장될 파일자동 채점이나 반복 테스트에서 유용하다.
gcc main.c util.c -o program
또는 오브젝트 파일 분리:
gcc -c main.c # main.o
gcc -c util.c # util.o
gcc main.o util.o -o program
all: main
main: main.o util.o
gcc main.o util.o -o main
main.o: main.c
gcc -c main.c
util.o: util.c
gcc -c util.c
clean:
rm -f *.o main
사용:
make # 빌드
make clean # 정리
명령어 | 설명 |
---|---|
info locals | 현재 함수 내 지역변수 보기 |
info args | 현재 함수의 인자 보기 |
backtrace | 함수 호출 스택 추적 |
watch 변수명 | 변수 값이 바뀔 때 자동 중단 |