Jungle

[TIL][Krafton Jungle] 4주차 - (2) : 포인터와 디버깅

손가든 2023. 11. 4. 00:47

 

C컴파일/디버깅/make 사용법 학습

 

make 사용법을 학습하라는데, make가 뭔지 모른다..
 
그래서 찾아봤다.
 
make는 C언어 프로젝트를 빌드하고 컴파일하는 빌드 자동화 도구이다.
 
여러 파일간의 종속성을 관리하면서, 특정 소스 코드만 변경될 경우 필요한 파일만 다시 컴파일하면서 빠르게 재빌드 할 수 있게 해준다.
 
다음은 간단한 C 프로젝트를 위한 Makefile의 예시이다.

 

CC = gcc
CFLAGS = -Wall -O2
TARGET = myprogram
SOURCES = main.c helper.c
OBJECTS = $(SOURCES:.c=.o)

$(TARGET): $(OBJECTS)
	$(CC) $(CFLAGS) -o $(TARGET) $(OBJECTS)

%.o: %.c
	$(CC) $(CFLAGS) -c $< -o $@

clean:
	rm -f $(OBJECTS) $(TARGET)

 

위 Makefile에서는 main.c 와 helper.c 두 소스파일을 컴파일하여 myprogram이라는 실행 파일을 생성한다.
 
위에서 부터 해당 명령어를 살펴보면,
 
CC컴파일러를 지정.
CFLAGS컴파일 옵션 지정.
TARGET최종 생성되는 실행파일의 이름
SOURCES 프로젝트의 소스 파일들
OBJECTS는 소스 파일을 컴파일한 객체 파일의 이름을 지정한다.
 
실제 사용한다고 생각하면, 위의 파일을 원하는 조건에 맞게 이름을 설정한 뒤,
해당 프로젝트가 있는 폴더 디렉토리에 해당 파일을 두고, VSC라면 IDE 안 터미널에서 `make` 명령어를 치면
TARGET 파일이 생성된다.
 
그러면 TARGET파일을 실행하면 된다. 빌드된 실행파일의 실행은 `./{실행파일이름}` 명령어로 실행시킬 수 있다.
 


 

C 포인터 개념 이해하기

 
C 에서 모든 변수들은 컴퓨터의 메모리에 저장되는데
저장되는 해당 위치는 주소라는 값을 가진다. { 64bits 예시 ) 0x00000000FFFF089E }
 
포인터 변수란 해당 변수가 위치한 주소값을 저장하는 변수이다.
 
이 포인터를 이용해서 간접 참조라는 것이 가능한데,

#include <stdio.h>


int main(){
	int i = 300;
    	int* p = NULL;
    	p = &i;
    
    	printf("%p",p);
    	printf("%p", &i);
    	printf("%d",i);
    	printf("%d", *p);
	return 0;
}

 
이렇게 하면 위 두줄의 print는 i가 저장되어 있는 메모리의 주소를 출력하고,
밑의 두줄은 i의 실제 값을 출력한다.
 
4번째 print에서의 포인터에 *을 달아 해당 값을 가져오는 방식을 간접 참조 라고 한다.
 
해당 주소를 통한 값을 확인 하는 방식이기 때문에 실제 데이터를 가져온 뒤 수정하는 것이 아닌,
메모리 내의 실제 값을 가지고 다루는 행위이다. (해당 경우는 그저 출력했을 뿐이지만)
 
이 포인터를 이용해서 2차원 배열을 이중 포인터를 통해서 표현 할 수 있다.
 
다음은 동적 할당을 사용해서 배열처럼 사용한 포인터의 예시 코드이다. 
 

int* ptr = NULL;
ptr = (int*)malloc(sizeof(int)*5);

 
이렇게 하면 5개의 인덱스를 가진 int형 배열을 동적 할당 할 수 있다.
 
이번엔 2차원 배열(5X5)을 동적 할당하는 코드를 보자 

int** ptr=NULL;
ptr = (int**)malloc(sizeof(int*)*5);
for(int i=0;i<5;i++)
   ptr[i] = (int*)malloc(sizeof(int)*5);

 
 
코드를 보면 이중 포인터에 동적으로 5개의 포인터가 들어가는 배열을 할당 해 주고
포인터 배열의 인덱스에 각각 int형 배열의 주소를 대입하는 방식을 이용했다.
 
이처럼 포인터 배열의 요소들에 각각 5개짜리 int형 배열의 첫번째 주소값을 할당하여 이차원 배열을 만들어냈다.
 

이중 포인터 배열 그림

 
이렇게 동적 할당을 malloc으로 선언해주었으면 해제도 잘 해줘야 한다.
 
다음은 이중 포인터를 선언한 코드의 해제 코드이다.
 

for (int i = 0; i < 5; i++)
	free(ptr[i]);
free(ptr);

 

 Q. 왜 `free()` 를 사용해서 메모리를 해제해야 할까?

 
동적으로 할당된 메모리는 해제하지 않으면 프로그램이 실행을 마쳐도 사용하지 않은 메모리를 계속 차지하고 있어서, 시스템 리소스를 낭비하고, 프로그램이 실행 중에 메모리 부족 오류를 유발할 수도 있다.
 
또한 메모리를 해제하지 않으면 프로그램이 예상하지 못한 동작을 할 수도 있는데, 이를 방지하고 예측 가능성을 향상하는 측면에서도 메모리를 해제하는 것이 중요하다. 만약 해제하지 않은 메모리를 다른 곳에서 할당하며 접근하려고 시도하면, 오류가 발생할 수 있다.
 
마지막으로 메모리 할당과 해제가 코드에 명시적으로 나타나면, 메모리 관련 누수와 같은 오류의 원인을 찾고 수정하기 쉬워진다.