C언어를 공부하면서 여러 가지 개념을 배웠지만, 그중에서도 구조체, 함수, 동적할당은 특히 중요하게 느껴졌다. 처음에는 각각의 개념이 따로 떨어져 있는 내용처럼 보였지만, 공부를 하다 보니 세 개념 모두 데이터를 더 효율적으로 다루기 위해 사용된다는 공통점이 있다는 것을 알게 되었다.
이번 글에서는 C언어에서 구조체, 함수, 동적할당이 각각 어떤 역할을 하는지, 그리고 실제 코드에서 어떻게 함께 사용될 수 있는지 정리하도록 하겠다.
1. 구조체
구조체는 여러 개의 데이터를 하나로 묶어서 관리할 수 있게 해주는 문법이다.
예를 들어 학생 한 명의 정보를 저장한다고 하면 이름, 나이, 점수 같은 데이터가 필요하다. 이 데이터들을 각각 따로 변수로 만들 수도 있지만, 그렇게 하면 학생 한 명의 정보가 흩어져 있어서 관리하기 불편하다.
char name[20];int age;int score;이럴 때 구조체를 사용하면 관련 있는 데이터들을 하나의 묶음으로 만들 수 있다.
struct Student { char name[20]; int age; int score;};위 코드에서 Student 구조체는 학생 한 명의 정보를 저장하기 위한 자료형이다. 이름, 나이, 점수를 하나로 묶어서 관리할 수 있다.
1.1 구조체를 사용하는 이유
구조체를 사용하면 관련 있는 데이터를 하나의 단위로 다룰 수 있다.
예를 들어 학생 정보를 출력하는 프로그램을 만든다고 할 때, 구조체를 사용하지 않으면 이름, 나이, 점수를 따로 관리해야 한다. 하지만 구조체를 사용하면 학생 한 명의 정보를 하나의 변수처럼 다룰 수 있다.
struct Student s1 = {"Kim", 17, 90};구조체 안에 있는 값은 . 연산자를 사용해서 접근할 수 있다.
printf("%s\n", s1.name);printf("%d\n", s1.age);printf("%d\n", s1.score);즉, 구조체는 여러 데이터를 하나로 묶어 프로그램을 더 깔끔하게 만들 수 있게 해준다.
2. 함수
함수는 특정한 작업을 수행하는 코드 묶음이다.
프로그램을 작성하다 보면 같은 작업을 여러 번 해야 하는 경우가 많다. 이때 같은 코드를 계속 반복해서 작성하면 코드가 길어지고 수정하기도 어려워진다.
함수를 사용하면 특정 기능을 따로 분리해서 필요할 때마다 호출할 수 있다.
void printHello() { printf("Hello\n");}위 함수는 Hello를 출력하는 기능을 한다.
함수를 실행하려면 다음과 같이 호출하면 된다.
printHello();2.1 함수를 사용하는 이유
함수를 사용하면 코드를 역할별로 나눌 수 있다.
예를 들어 학생 정보를 입력하는 기능, 학생 정보를 출력하는 기능, 평균 점수를 계산하는 기능을 모두 main 함수 안에 작성하면 코드가 복잡해진다.
하지만 각각을 함수로 나누면 코드의 역할이 더 명확해진다.
void printStudent(struct Student s) { printf("이름: %s\n", s.name); printf("나이: %d\n", s.age); printf("점수: %d\n", s.score);}이 함수는 학생 정보를 출력하는 역할만 담당한다.
printStudent(s1);이렇게 함수를 사용하면 코드를 더 읽기 쉽게 만들 수 있고, 같은 기능을 여러 번 재사용할 수 있다.
3. 동적할당
동적할당은 프로그램이 실행되는 중에 필요한 만큼 메모리를 할당받는 방법이다.
일반 배열은 크기를 미리 정해야 한다.
int arr[5];위 코드는 정수 5개를 저장할 수 있는 배열을 만든다. 하지만 사용자가 몇 개의 데이터를 입력할지 미리 알 수 없는 경우에는 배열 크기를 고정하기 어렵다.
이럴 때 동적할당을 사용하면 실행 중에 필요한 크기만큼 메모리를 할당받을 수 있다. 동적할당 함수를 사용하려면 <stdlib.h> 헤더 파일이 필요하다.
#include <stdlib.h>
int *arr = malloc(sizeof(int) * n);위 코드는 정수 n개를 저장할 수 있는 공간을 동적으로 할당하는 코드이다.
3.1 malloc과 free
동적할당을 할 때는 malloc 함수를 사용한다.
int *arr = malloc(sizeof(int) * n);malloc은 필요한 만큼 메모리를 할당하고, 그 메모리의 시작 주소를 반환한다.
다만 메모리 할당이 항상 성공하는 것은 아니다. 따라서 malloc을 사용한 뒤에는 반환값이 NULL인지 확인하는 것이 좋다.
if (arr == NULL) { printf("메모리 할당 실패\n"); return 1;}동적할당으로 받은 메모리는 사용이 끝난 후 반드시 free로 해제해야 한다.
free(arr);arr = NULL;free를 하지 않으면 더 이상 사용하지 않는 메모리가 계속 남아 있을 수 있다. 이런 문제를 메모리 누수(Memory Leak)라고 한다. 또한 메모리를 해제한 후 포인터 변수를 NULL로 초기화해 주면 이미 해제된 메모리에 다시 접근하는 실수를 줄일 수 있다.
4. 구조체와 동적할당
구조체도 동적할당과 함께 사용할 수 있다.
예를 들어 학생 수를 사용자가 입력하게 하고, 그 수만큼 학생 정보를 저장하고 싶다면 구조체 배열을 동적할당할 수 있다.
struct Student *students = malloc(sizeof(struct Student) * count);위 코드는 Student 구조체 count개를 저장할 수 있는 공간을 동적으로 할당하는 코드이다. 이렇게 하면 학생 수가 정해져 있지 않아도 실행 중에 필요한 만큼 공간을 만들 수 있다.
students[0].age = 17;students[0].score = 90;동적할당한 구조체 배열도 일반 배열처럼 인덱스를 사용해서 접근할 수 있다.
구조체 변수로 멤버에 접근할 때는 .을 사용하고, 구조체 포인터로 멤버에 접근할 때는 ->를 사용한다.
students[0].age = 17; // 구조체 변수처럼 접근students->age = 17; // students[0].age와 같은 의미students->age는 (*students).age와 같은 의미이다. 즉, 포인터가 가리키는 구조체의 멤버에 접근하는 표현이다.
사용이 끝나면 다음과 같이 메모리를 해제해야 한다.
free(students);students = NULL;5. 구조체, 함수, 동적할당의 연결
구조체, 함수, 동적할당은 각각 따로 배우는 개념이지만 실제 프로그램에서는 함께 사용되는 경우가 많다.
예를 들어 학생 정보를 관리하는 프로그램을 생각해볼 수 있다.
구조체: 학생 정보를 하나로 묶음함수: 입력, 출력, 계산 기능을 나눔동적할당: 학생 수만큼 메모리를 할당함간단한 코드로 보면 다음과 같다.
#include <stdio.h>#include <stdlib.h>
struct Student { char name[20]; int age; int score;};
// 매개변수로 구조체의 주소를 받으면// 구조체 전체가 복사되지 않아 더 효율적으로 처리할 수 있다.void printStudent(const struct Student *s) { printf("이름: %s\n", s->name); printf("나이: %d\n", s->age); printf("점수: %d\n", s->score);}
int main() { int count;
printf("학생 수 입력: "); scanf("%d", &count);
if (count <= 0) { printf("학생 수는 1명 이상이어야 한다.\n"); return 1; }
// 학생 수만큼 구조체 동적할당 struct Student *students = malloc(sizeof(struct Student) * count);
if (students == NULL) { printf("메모리 할당 실패\n"); return 1; }
for (int i = 0; i < count; i++) { printf("%d번째 학생 이름: ", i + 1); scanf("%s", students[i].name);
printf("%d번째 학생 나이: ", i + 1); scanf("%d", &students[i].age);
printf("%d번째 학생 점수: ", i + 1); scanf("%d", &students[i].score); }
printf("\n--- 학생 정보 출력 ---\n");
for (int i = 0; i < count; i++) { // 주소값을 넘겨주기 위해 & 연산자를 사용한다. printStudent(&students[i]); }
// 메모리 해제 및 초기화 free(students); students = NULL;
return 0;}위 코드에서는 학생 정보를 저장하기 위해 구조체를 사용했고, 학생 정보를 효율적으로 출력하기 위해 구조체 포인터를 매개변수로 갖는 함수를 사용했다. 또한 학생 수만큼 공간을 만들기 위해 동적할당을 사용했다.
즉, 구조체, 함수, 동적할당은 서로 따로 떨어진 개념이 아니라 하나의 프로그램 안에서 함께 사용될 수 있는 개념이다.
이번 글에서는 C언어에서 중요한 개념인 구조체, 함수, 동적할당에 대해 정리해보았다.
구조체는 여러 데이터를 하나로 묶기 위해 사용하고, 함수는 코드를 역할별로 나누기 위해 사용한다. 동적할당은 프로그램 실행 중에 필요한 만큼 메모리를 사용할 수 있게 해준다.
처음에는 구조체, 함수, 동적할당이 각각 다른 개념처럼 느껴졌지만, 공부를 하면서 이 개념들이 서로 연결되어 있다는 것을 알게 되었다. 특히 구조체 배열을 동적할당하고, 그 데이터를 함수로 처리하는 과정을 통해 C언어에서 데이터를 다루는 방식에 대해 더 깊게 이해할 수 있었다.