본문 바로가기

Daylogs/C

C: 메모리 동적 할당, 선행처리기, 매크로, 헤더 파일

이전 글: 9. 파일 입출력에서 계속



메모리의 구성
- 코드 영역: 실행할 프로그램의 코드가 저장되는 메모리 공간, CPU는 코드 영역에 저장된 명령문들을 하나씩 가져가서 실행한다.
- 데이터 영역: 전역변수와 static으로 선언되는 static 변수가 할당. 프로그램 종료 시까지 남는다.
- 스택 영역: 지역변수와 매개변수가 할당된다. 함수가 종료되면 소멸된다.
- 힙 영역: 프로그래머가 원하는 시점에 변수를 할당하고 소멸하도록 지원하는 공간



malloc 과 free

생성과 소멸 시기가 지역변수나 전역변수와 다른 유형의 변수는,
malloc 과 free 함수로 힙 영역에 할당하고 소멸할 수 있다.

먼저, stdlib.h를 추가해준다.

#include <stdlib.h>

void * malloc(size_t size); // 힙 영역으로 메모리 공간 할당
--> 성공 시 할당된 메모리 주소값, 실패 시 NULL 반환

void free(void * ptr); // 힙 영역에서 메모리 공간 해제

// 아래와 같이 사용한다.
int * ptr1 = (int *)malloc(sizeof(int));
*ptr1 = 20;


malloc이 호출된 개수만큼 free도 호출해줘야 한다.



calloc

void * calloc(size_t elt_count, size_t elt_size); // 블럭 개수만큼 반환
--> 성공 시 할당된 메모리 주소값, 실패 시 NULL 반환

malloc으로 구현 시 아래와 동일

char * ptr2 = (char *)malloc(30, 4); 
// 4바이트 크기의 블럭 30개를 힙 영역에 할당해주세요.



realloc

void * realloc(void * ptr, size_t size);
--> 성공 시 새로 할당된 메모리 주소 값, 실패 시 NULL 반환

"ptr이 가리키는 메모리의 크기를 size 크기로 조절해줘"
(아마도, 동적 배열에 사용될 것 같다)



선행처리기(Preprocessor)와 매크로(Macro)

선행처리 = 컴파일 이전의 처리를 의미

소스파일 --> 선행처리기 --> 선행처리를 거친 소스파일 --> 컴파일러 --> 오브젝트 파일 --> 링커 --> 실행파일


선행처리는 대부분 단순 치환의 형태를 띠며, #로 시작한다.

#define PI 3.14
"소스코드에서 PI를 만나면 바로 3.14로 치환하라."
(소스코드가 직접 바뀐다고 생각하면 된다)

#include <stdio.h>
"stdio.h의 파일의 내용을 여기다 옮겨놓아라."



#define: Object-like macro

#define    PI         3.14
--------    ---         -------
지시자     매크로    매크로 몸체(또는 대체 리스트)

위와 같은 PI를 '오브젝트와 유사한 매크로' 또는 '매크로 상수'라고 한다.

매크로는 대문자로 작성한다.



#define: Function-like macro

매크로는 매개변수가 존재하는 형태로도 정의할 수 있다.
'함수와 유사한 매크로', '매크로 함수'라고도 한다.

#define     SQUARE(X)              X*X
--------     --------------             ------
                이런 패턴 등장 시       이렇게 바꿔라


이렇게 선행처리기에 의해서 변환되는 과정 자체를 가리켜 '매크로 확장(macro expansion)'이라 한다.

!! 주의
선행처리기는 컴파일러 이전에 변경되므로, 매크로 전체를 괄호로 묶어주는 것이 중요하다.
#define SQUARE(X)  ((X) * (X))

두 줄로 정의하고자 할 때에는 이스케이프 문자(\)를 사용한다.


또 하나, 먼저 정의된 매크로도 사용가능하다.
#define CIRCLE(R)   ( 2 * PI * ( R ) )



매크로의 장단점

장점:
+ 일반 함수에 비해 실행 속도가 빠르다.
    스택 메모리 할당 및 인자 전달 등을 거치지 않기 때문이다.
+ 자료형에 따라 별도로 함수를 정의하지 않아도 된다.
   자료형에 관계없이 문자 그대로 치환되기 때문이다.

단점:
- 정의하기가 까다롭다.
- 디버깅이 어렵다.

따라서 주로 이런 함수들을 매크로로 정의한다.
- 작은 크기의 함수
- 호출 빈도가 높은 함수



조건부 컴파일

특정 조건에 따라 소스코드의 일부를 삽입하거나 삭제하기 위한 용도

#if FOO
     // 매크로 FOO가 참이라면
#endif

#ifdef FOO
     // 매크로 FOO가 정의되었다면
#endif

#ifndef FOO
     // 매크로 FOO가 정의되지 않았다면
#endif
// 이 매크로는 주로 헤더파일의 중복을 막기 위해 사용된다.

#if FOO == 1
     … 
#elif FOO == 2
     … 
#else
     … 
#endif



매크로 매개변수의 문자 치환

#define STRING_JOB(A, B)  #A "'s job is " #B

문자열 내에서 매개변수 치환을 위해선 # 연산자를 사용해야 한다.
#A 는 "A"와 같은 형태로 치환된다.

문자열은 연속해서 나란히 선언하면 하나의 문자열로 간주된다.
예) char * str = "FOO" "BAR" --> "FOOBAR"와 동일


#define CONCAT(A, B, C) A ## B ## C

매개변수의 단순 연결에는 ## 연산자를 사용한다.

CONCAT(10, 2, 030) --> 102030



파일의 분할과 헤더파일 디자인

extern int num; // int형 변수 num이 외부에 선언되어 있다.

extern void increment(void); // 함수가 외부에 정의되어 있다.
void increment(void); // 함수의 경우 생략 가능하다.



static 키워드

static 지역변수 : 변수를 데이터 영역 메모리 공간에 정의한다.

static 전역변수 : 이 변수의 접근 범위는 파일 내부로 제한한다.
                         즉, 외부 파일에서 접근할 수 없다.

static 함수 : static 전역변수와 동일하다.



#include 지시자

#include "header1.h" // 이 문장 위치에 header1.h 파일의 내용을 가져다 놓으세요.
--> 이 문장을 포함하는 소스 파일 기준에서 상대경로로 찾는다.

#include <stdlib.h> // C가 제공하는 표준 헤더파일에서 찾는다.



헤더파일!

헤더파일에는 중복 정의될 수 있는 내용을 넣는다.

- 매크로 : 매크로의 명령문도 파일 단위로만 유효하기 때문에 헤더 파일에 삽입하는 것이 편리하다.
- 함수 선언 : 외부에서 가져다 쓸 용도의 함수 선언
- 구조체 정의 



헤더 파일의 중복 삽입 해결법

구조체의 선언 및 정의 시 헤더파일에 넣는 것은 좋지만, 중복되면 컴파일 에러가 난다.
중복 방지를 위해 조건부 컴파일을 활용한다.

헤더파일: stperson.h

#ifndef __STPERSON_H__
#define __STPERSON_H__

typedef struct
{
  int age;
  char sex;
  char name[20];
  
} PERSON;

#endif