코딩 학습/C와 C++

C - 동적 할당, 구조체

이개 2026. 3. 3. 11:20

메모리 레이아웃(메모리 구조)

메모리 레이아웃은 크게 스택 메모리, 힙 메모리, 코드 섹션, 데이터 섹션으로 나뉩니다. 이번 단원에서 살펴볼 메모리는 힙 메모리입니다.

 

 

스택 메모리의 단점

1️⃣ 스택 메모리에 저장되는 지역 변수(배열 등)의 크기는 컴파일 타임에 고정됩니다. 런타임에 더 필요해진다 해도 크기를 늘릴 수 없습니다.

2️⃣ 함수가 종료되면 해당 스택 프레임에 더이상 접근 불가능합니다. 즉, 지역변수의 수명은 함수의 수명과 함께합니다. 더 오래 보존하려면 전역변수 혹은 정적변수로 선언해야 합니다. 근데 이 변수들은 너무 또 극단적입니다. 프로그램의 수명과 함께합니다.

 

📌 런타임 시에 프로그래머가 원하는 만큼(1번 만족) 원하는 때에 생성 및 삭제(2번만족) 가능한 메모리가 필요합니다.

 

 

힙 메모리(Heap Memory)

프로그래머의 메모리 할당과 해제를 통해 관리되는 동적 할당 영역입니다.

스택 메모리는 함수의 호출 및 종료에 따라 자동으로 정리됩니다만, 힙메모리는 아닙니다. 프로그래머가 원하는 만큼, 원하는 때에 할당 및 반납이 가능합니다. 이것이 힙 메모리의 장점입니다.

 

힙 메모리의 단점

1️⃣ 스택 메모리에 비교해 할당/해제 속도가 느립니다. 스택 메모리는 자료구조 스택의 특성상 할당 및 해제에 O(1) 시간이 걸립니다. 힙 메모리는 할당 받아오려면 사용중이지 않은 메모리이면서 크기가 맞는지 체크 후 제공됩니다. 또한 메모리 공간에 구멍(메모리 단편화)이 생길 수도 있어서 효율적인 메모리 관리가 어렵기도 합니다.

2️⃣ 프로그래머가 직접 메모리 할당 및 해제 해야합니다. 메모리 할당만 하고 해제는 안하는 실수를 할 여지가 있습니다.

 

 

동적 할당의 세 가지 단계

1️⃣ 메모리 할당(대여) 힙 메모리 관리자에게 필요한 바이트만큼의 메모리를 달라고 요청합니다. 힙 메모리 관리자는 해당 크기의 연속된 메모리를 찾아서 반환합니다. 반환된 값은 시작 메모리 주소입니다.

2️⃣ 메모리 사용 할당된 힙 메모리 시작 주소를 가지고 원하는 작업 수행합니다. 이때 할당된 메모리 속 데이터는 쓰레기값입니다.

3️⃣ 메모리 해제(반납) 힙 메모리 관리자에게 해당 메모리 주소를 돌려주면서 다 썼다고 알립니다. 힙 메모리 관리자는 해당 메모리를 점유되지 않은 메모리 상태로 바꿉니다. 메모리 주소를 돌려주지 않으면 메모리 누수(Memory leak) 발생합니다. 메모리 누수란, 해당 메모리가 점유 상태를 벗어나지 못해 사용가능한 메모리가 줄어드는 현상입니다.

 

동적 메모리 관련 함수

할당: malloc() / calloc() 재할당: realloc() 해제: free() 기타: memset() / memcpy() / memcmp() / …

 

void* malloc(size_t size)

memory allocation의 약자. stdlib.h 헤더파일에 선언되어 있습니다. size 바이트 만큼의 메모리를 반환해줍니다. 할당 실패시 NULL을 반환합니다.

 

void free(void* ptr)

동적 할당 받은 메모리를 해제하는 함수. 다시말해, 메모리 할당 함수를 통해서 얻은 메모리 주소만 해제 가능합니다.

 

[좋은습관] malloc()을 작성했다면 곧바로 free()부터 작성하기.

동적 할당 받은 메모리 주소를 지역 변수에 저장해뒀다가 해제 안하고 함수가 종료되버리면, 해당 지역변수를 접근할 방법이 사라져 버립니다. 지울 방법이 없습니다. 이렇게 반납하지 않으면 메모리 누수가 발생하므로, 무슨 일이 있어도 free()부터 작성합시다.

 

예시 코드

#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>
#include <stdlib.h>


int main(void) 
{
	size_t i; // 시스템이 32비트인 경우 unsigned long, 64비트일 경우 unsigned long long 타입을 size_t라고 함


	int* Nums;

	Nums = (int*)malloc(10 * sizeof(int));

	if (NULL == Nums) {
		return 0;
	}

	for (i = 0; i < 10; ++i)
	{
		Nums[i] = i * 10;
	}

	for (i = 0; i < 10; ++i)
	{
		printf("%d", Nums[i]);
	}

	free(Nums);
	Nums = NULL; // free만 하지 말고 NULL 초기화까지 해주어야 한다.

	if (NULL != Nums)
	{
		//
	}

	return 0;
}

동적 할당 받은 메모리 시작 주소를 연산에 사용한다면 어떻게 될까요?

최초에 받아온 시작 주소를 잃어버리게 됩니다. 그럼 다른 메모리 주소를 free() 함수의 인자로 보낼지도 모릅니다. 사본을 만들어서 포인터 연산에 사용해야 합니다.

 

 

 

구조체

수업 목표

  • 구조체가 무엇인지 배워본다.
  • 구조체와 객체지향 프로그래밍의 공통점에 대해 알아본다. </aside>

 

구조체(Structure)

필요한 여러 자료형의 변수들을 한데 묶어서 하나의 자료형처럼 만들 수 있습니다.

아래와 같은 코드로 정의 가능합니다. 예제 코드를 반복적으로 클론 코딩 해보며 숙지해봅시다.

struct 구조체명 
{
	자료형 변수명;
	...
};

 

 

. 연산자를 구조체 멤버 접근 연산자라고 합니다. 

// Main.c

#include <stdio.h>

struct Date
{
	int Year;  // 구조체 Date의 멤버 Year.
	int Month; // 구조체 Date의 멤버 Month.
	int Day;   // 구조체 Date의 멤버 Day.
};

int main(void)
{
	struct Date Birthday;
	Birthday.Year = 2024;
	Birthday.Month = 1;
	Birthday.Day = 13;

	printf("%d/%d/%d\\n", Birthday.Year, Birthday.Month, Birthday.Day);

	return 0;
}

 

 

Ex090104) 함수의 인자로 구조체 변수 전달 [중요 샘플 코드]

아래 소스코드를 따라서 작성해봅시다.

// Main.c

#include <stdio.h>

struct Date
{
	int Year;
	int Month;
	int Day;
};

void PrintBirthday(struct Date InBirthday);

int main(void)
{
	struct Date Birthday;
	Birthday.Year = 2024;
	Birthday.Month = 1;
	Birthday.Day = 13;
		// 구조체 변수 Birthday는 어느 메모리에 저장 될까요?.
		// 당연하게도 스택 메모리입니다. 다른 기본 자료형과 마찬가지입니다.
		// 초기화 하지 않으면 0이 아닌 쓰레기 값이 저장되어 있습니다.

	PrintBirthday(Birthday);

	return 0;
}

void PrintBirthday(struct Date InBirthday)
{
	printf("%d/%d/%d\\n", InBirthday.Year, InBirthday.Month, InBirthday.Day);
}

 

Ex090105) 문제의 코드

아래 소스코드를 따라서 작성해봅시다. 문제가 될만한 부분이 어딘지 고민해보고 실행해봅시다.

// Main.c

#include <stdio.h>

struct Date
{
	int Year;
	int Month;
	int Day;
};

void PrintBirthday(struct Date InBirthday);

int main(void)
{
	struct Date Birthday;

	PrintBirthday(Birthday);

	return 0;
}

void PrintBirthday(struct Date InBirthday)
{
	printf("%d/%d/%d\\n", InBirthday.Year, InBirthday.Month, InBirthday.Day);
}

 

 

위의 코드는 초기화가 되어있지 않아 정상동작하지 않는다. 변수 초기화를 해주어야한다. C++에서는 Birthday = {0};만으로도 초기화 가능하지만, C에서는 struct Date Birthday = {0}; 처럼 선언할 때만 초기화 가능하다.

 

 

 

 

 

Java만 하다보니 C 문법에 아직 익숙하지 않아서 이제부터 열심히 해야겠다.

C를 한지 너무 오래되다보니 string이랑 ArrayList, Map이 없다는 것이 충격적이었는데 다행히 C++에는 있다고한다.

  • std::string

이러나 저러나 포인터, 구조체랑 동적할당은 다시 체계적으로 정리해야겠다.

 

  1. int*p, &p, *p 사용법 까먹지 말자. 각각 선언, 변수의 주소, 역참조 문법이다.
  2. 구조체 선언 중괄호 뒤에 ; 넣어줘야한다.
  3.  typedef로 긴 타입명을 편하게 정의할 수 있다. typedef로 정의한 구조체는 접미사 _t를 붙여준다.
    ex) struct Date Birthday01; 
     -> typedef struct Date Date_t; 또는 처음 구조체 선언할 때부터 typedef struct Date {} Date_t;

 

 

학교 다닐 때 offset 기준으로 메모리 영역에 있는 주소와 데이터 찾기가 시험문제로 나왔던 걸로 기억하는데... 기억이 나는 것 같기도 하고...

'코딩 학습 > C와 C++' 카테고리의 다른 글

C++ 기초 - 포인터와 레퍼런스  (0) 2026.03.05
C++ 기초 - Visual Studio 실행, 변수, 입출력  (0) 2026.03.04
C - 배열  (0) 2026.03.04
C 언어 연습  (0) 2026.03.03
C - 포인터  (1) 2026.03.03