티스토리 뷰
C 언어에서 값 전달, 포인터, 구조체 설계 방식 정리
코드를 짤 때, 데이터를 어떻게 전달하고 관리할지는 꽤 중요한 선택입니다.
값을 직접 넘길지, 주소를 넘길지, 혹은 구조체로 묶을지에 따라 코드의 구조와 효율이 달라지니까요.
이 글에서는 다음 네 가지 방식에 대해 정리해 보았습니다.
- 값 그대로 전달
- 포인터 이용해서 전달
- 구조체 안의 구조체
- 주소 넘기기 + memcpy()
1. 값 그대로 전달
가장 단순한 방식입니다. 함수에 값을 넘기면, 그 값은 복사되어 함수 내부에서 사용됩니다.
즉, 원본에는 영향을 주지 않습니다.
#include <stdio.h>
void doubleValue(int x) {
x = x * 2;
printf("Inside function: %d\n", x); // 20
}
int main() {
int num = 10;
doubleValue(num);
printf("After function: %d\n", num); // 10
return 0;
}
2. 포인터 이용해서 전달
값이 아닌 주소를 넘기는 방식입니다. 함수 내부에서 원본 값을 직접 수정할 수 있습니다.
#include <stdio.h>
void doubleValue(int* x)
{
*x = *x * 2;
}
int main()
{
int num = 10;
doubleValue(&num);
printf("After function: %d\n", num); // 20
return 0;
}
포인터를 활용하면 메모리도 아끼고, 값 수정도 가능합니다.
단, 포인터 실수에 의한 오류에는 주의해야 합니다.
3. 구조체 안의 구조체
연관된 데이터를 하나로 묶고 싶을 때 구조체는 좋은 도구입니다.
그리고 여러 곳에서 반복되는 구조가 있다면, 구조체 안에 구조체를 넣어 재사용할 수 있습니다.
#include <stdio.h>
typedef struct {
int year;
int month;
int day;
} Date;
typedef struct {
char name[20];
Date birthday;
} Person;
typedef struct {
char productName[20];
Date manufacturedDate;
} Product;
void printPerson(Person* p) {
printf("Name: %s\n", p->name);
printf("Birthdate: %d-%02d-%02d\n",
p->birthday.year, p->birthday.month, p->birthday.day);
}
void printProduct(Product* p) {
printf("Product: %s\n", p->productName);
printf("Manufactured: %d-%02d-%02d\n",
p->manufacturedDate.year, p->manufacturedDate.month, p->manufacturedDate.day);
}
int main() {
Person alice = { "Alice", {1990, 12, 25} };
Product phone = { "PhoneX", {2024, 3, 10} };
printPerson(&alice);
printProduct(&phone);
return 0;
}
Date라는 구조를 반복 선언하지 않고 한 번만 정의해서 여러 구조체에 넣을 수 있기 때문에 중복 제거와 유지보수 측면에서 훨씬 유리합니다.
4. 주소 넘기기 + memcpy()로 매핑
하나의 패킷 안에 다양한 형태의 데이터를 담아야 할 때는
유연하게 처리할 수 있는 구조가 필요합니다. union을 쓰지 못하는 상황이라면, void* 포인터와 memcpy()를 이용해 원하는 구조체로 직접 매핑할 수 있습니다.
#include <stdio.h>
#include <stdint.h>
#include <string.h>
typedef struct {
int id;
float value;
} SensorData;
typedef struct {
char msg[32];
} TextData;
typedef struct {
uint8_t type; // 0: SensorData, 1: TextData
void* data;
} Packet;
void handlePacket(Packet* pkt) {
if (pkt->type == 0) {
SensorData s;
memcpy(&s, pkt->data, sizeof(SensorData));
printf("Sensor id: %d, value: %.2f\n", s.id, s.value);
} else if (pkt->type == 1) {
TextData t;
memcpy(&t, pkt->data, sizeof(TextData));
printf("Message: %s\n", t.msg);
}
}
int main() {
SensorData s = { 101, 25.6f };
TextData t = { "Hello, World!" };
Packet p1 = { 0, &s };
Packet p2 = { 1, &t };
handlePacket(&p1);
handlePacket(&p2);
return 0;
}
이 방식의 장점은 구조체를 통째로 넘기는 것보다 더 유연하고 확장성 있는 방식으로 데이터를 처리할 수 있다는 점입니다.
정리 – 어떤 방식이 언제 유리할까?
방식 | 주요 특징 | 장점 | 단점 | 대표 상황 |
값 그대로 전달 | 값 복사 | 간단하고 안전 | 원본 수정 불가 | 정수, 부동소수 등 기본형 인자 |
포인터로 전달 | 주소 전달 | 수정 가능, 메모리 절약 | 포인터 실수에 주의 필요 | 배열, 구조체 수정 함수 등 |
구조체 안의 구조체 | 구조 계층화, 중복 구조 재사용 | 가독성↑, 유지 보수 편리 | 구조가 복잡해질 수 있음 | 날짜, 좌표 등 공통 데이터 활용 시 |
주소 넘기기 + memcpy() | 다양한 구조를 하나의 틀로 유연하게 처리 | 확장성과 유연성 확보 | 타입 안정성 직접 관리 필요 | 패킷 처리, 메시지 인터페이스 등 |