Struct
"오늘의AI위키"의 AI를 통해 더욱 풍부하고 폭넓은 지식 경험을 누리세요.
1. 개요
구조체는 서로 다른 데이터 형식을 묶어 새로운 데이터 형식을 정의하는 방법으로, 연관된 데이터를 논리적인 단위로 관리할 수 있게 해준다. C/C++에서는 `struct` 키워드를 사용하여 구조체를 정의하며, 멤버 접근에는 `.` 또는 `->` 연산자를 사용한다. 구조체는 코드의 가독성과 유지보수성을 높이고, 데이터 추상화 및 캡슐화를 통해 객체 지향 프로그래밍을 지원한다. .NET 언어에서는 C#의 `struct`와 VB.NET의 `Structure`를 통해 구조체를 제공하며, 값 형식으로 동작하여 소규모 데이터 구조 정의에 적합하다. 또한, API 설계에서 구조체는 캡슐화를 위해 불투명 타입으로 사용되기도 한다.
구조체는 서로 다른 자료형(data type)의 변수들을 하나로 묶어 새로운 자료형을 정의하는 방법이다. 이는 연관된 데이터를 하나의 논리적인 단위로 관리할 수 있게 해준다.
c
2. 구조체의 개념 및 필요성
예를 들어 사람의 정보를 처리할 때, 이름, 나이, 성별, 전화번호와 같이 공통으로 추출 가능한 정보들을 묶어서 처리할 수 있다. C 언어에서는 `struct` 키워드를 사용하여 다음과 같이 구조체를 정의한다.
```c
struct Man {
char name[50];
int age;
char gender;
char tel[50];
};
```
C#, VB.NET, F#과 같은 언어들에서도 구조체를 지원한다.
만약 구조체를 사용하지 않고 변수들을 분리해서 사용하면 변수 관리가 복잡해지고, 오류 발생 가능성이 높아지며, 코드의 유연성이 떨어지는 등의 문제가 발생할 수 있다.
2. 1. 구조화의 필요성
데이터를 구조화하지 않으면 변수들이 분산되어 관리하기 어렵고, 오류 발생 가능성이 높아지며, 코드의 유연성이 떨어진다는 요약이 있으므로 해당 내용을 반영한다.
예를 들어 인간의 정보 처리를 한다면, 사람들의 공통된 정보를 추출한 다음 이것들을 묶어 처리한다. 만약 정보 처리 시, 변수들이 다음과 같이 분리되어 있다면 변수들이 상당히 복잡하게 나뉘고 관리된다.
```c
// file : mInfo.c
char name[100][50];
int countuse;
int age[100];
char gender[100];
int getUseCount()
{
return countuse;
}
```
```c
// file : mngTel.c
char tel[200][50];
char *getUseCount(int nId)
{
return tel[nId];
}
```
```c
// file : main.c
#include
extern int countuse;
int main(int argc, char**argv)
{
countuse = 10;
// ...
int num = getUseCount();
printf("NO Member+%d\n", num);
// ...
return 0;
}
```
위와 같이 3개의 프로그램 코딩 파일을 하나의 프로젝트로 묶어 개발할 때, 연관된 사람이라는 정보가 나뉘어 있어 동시에 여러 가지 파일을 검토하고 나누어 프로그램해야 한다. 이렇게 되면 복잡한 시스템에서 오류가 증가되고 유연성이 떨어진다.
따라서 변수 선언 시, 의미적으로 연결된 변수끼리 묶어서 선언해야 한다.
```c
struct Man {
char name[50];
int age;
char gender;
char tel[50];
};
struct Man man;
```
위의 예에서 인간의 정보 처리 시, 한 사람의 정보를 구조체로 묶어 정보 처리한다. 각각의 변수가 나뉘면 서로 연관 관계의 일관성 유지에 불리하다.
2. 2. 구조체의 장점
구조체는 연관된 데이터를 묶어 관리함으로써 코드의 가독성과 유지보수성을 향상시킨다. 예를 들어, 사람의 정보를 처리할 때 이름, 나이, 성별, 전화번호 등의 공통된 정보를 추출하여 하나의 구조체로 묶을 수 있다.
만약 이러한 정보들이 다음과 같이 분리되어 있다면:
```c
// file : mInfo.c
char name[100][50];
int countuse;
int age[100];
char gender[100];
int getUseCount()
{
return countuse;
}
```
```c
// file : mngTel.c
char tel[200][50];
char *getUseCount(int nId)
{
return tel[nId];
}
```
```c
// file : main.c
#include
extern int countuse;
int main(int argc, char**argv)
{
countuse = 10;
// ...
int num = getUseCount();
printf("NO Member+%d\n", num);
// ...
return 0;
}
```
3개의 파일을 하나의 프로젝트로 묶어 개발할 때, 사람의 정보를 처리하는 변수들이 복잡하게 나뉘어 관리된다. 이렇게 되면 '사람'이라는 연관된 정보가 분산되어 여러 파일을 동시에 검토하고 나누어 프로그래밍해야 하므로, 복잡한 시스템에서는 오류가 증가하고 유연성이 떨어진다.
하지만 구조체를 사용하면 의미적으로 연결된 변수들을 묶어서 선언할 수 있다.
```c
struct Man {
char name[50];
int age;
char gender;
char tel[50];
};
struct Man man;
```
위의 예시처럼 사람의 정보를 구조체로 묶어 처리하면, 각 변수가 분리되어 있을 때보다 연관 관계의 일관성을 유지하기에 유리하다. 또한, 데이터 추상화 및 캡슐화를 통해 객체 지향 프로그래밍을 지원한다.
3. 구조체의 선언 및 사용
/* Define a type point to be a struct with integer members x, y */
typedef struct {
int x;
int y;
} point;
/* Define a variable p of type point, and initialize all its members inline! */
point p = {1,2};
```
`struct`는 선언부와 변수부로 나누어 선언할 수 있다. 선언부는 컴파일러에게 구조체의 구조를 알려주는 기능을 하며, `#include` 시 중복 선언이 가능하다. 선언부 만으로는 변수의 저장공간을 확보하지 않기 때문이다.
구조체 선언의 구문은 다음과 같다.
```c
struct 태그_이름 {
자료형 멤버1;
자료형 멤버2;
};
```
`태그_이름`은 생략 가능하다.
`typedef` 키워드를 사용하면 `struct` 키워드 없이 구조체 형식을 참조할 수 있다.
예를 들어:
```c
typedef struct tag_name {
type member1;
type member2;
} thing_t;
thing_t thing;
```
C++ 코드에서는 `typedef`가 필요하지 않으며, `struct thing_t` 또는 `thing_t`로 형식을 참조할 수 있다.
구조체를 초기화하는 방법에는 세 가지가 있다.
```c
struct point_t {
int x;
int y;
};
```
```c
struct point_t a = { 1, 2 };
```
```c
struct point_t a = { .y = 2, .x = 1 };
```
```c
struct point_t b = a;
```
구조체의 상태는 다른 인스턴스로 복사될 수 있다. 컴파일러는 메모리 블록의 바이트를 복사하기 위해 `memcpy()`를 사용할 수 있다.
```c
struct point_t a = { 1, 3 };
struct point_t b;
b = a;
```
포인터를 사용하여 구조체의 주소를 참조할 수 있다. 이는 구조체를 함수에 전달하여 구조체 복사 오버헤드를 피하는 데 유용하다. `->` 연산자는 역참조하여 포인터(왼쪽 피연산자)를 참조하고 구조체 멤버의 값(오른쪽 피연산자)에 접근한다.
```c
struct point_t point = { 3, 7 };
int x = point.x;
point.x = 10;
struct point_t *pp = &point;
x = pp->x;
pp->x = 8;
3. 1. 구조체 선언
`struct` 키워드와 구조체 이름을 사용하여 구조체의 틀을 정의한다. C 언어에서 구조체 선언은 선언부와 변수부로 나누어 선언할 수 있다. 선언부는 컴파일러에게 구조체의 구조를 알려주는 기능을 하며, `#include` 시 중복 선언이 가능하다. 왜냐하면 선언부 만으로는 변수의 저장공간을 확보하지 않기 때문이다.
```c
// 코드 파일 명 : man.h
#ifndef _MAN_H
#define _MAN_H
struct Man {
char name[50];
int age;
char gender;
char tel[50];
};
struct Man *getMan();
#endif
```
```c
// 코드 파일 명 : man.c
#include "man.h"
struct Man man;
struct Man *getMan() { return &man; }
char *getName() { return man.Name; }
int getAge() { return man.age; }
char getGender() { return man.sex; }
char *getTel() { return man.tel; }
```
```c
// 코드 파일 명 : main.c
#include
#include "man.h"
int main(int argc, char**argv)
{
struct Man *pman;
pman = getMan();
printf("Name=%s\n", pman->name);
return 0;
}
```
```c
struct Man {
char name[50];
int age;
char gender;
char tel[50];
} man;
```
위와 같이 프로그램 구성을 하면 `man`이라는 변수의 메모리 공간이 만들어진다. 그러나 `Man` 구조체를 여러 파일에서 사용한다면 구조체의 구조부를 `include` 해야 하는데, 그렇게 되면 `man` 변수가 중복된다. 따라서 `include`는 한 번밖에 할 수 없다. 보통은 선언부만 분리해서 코딩하는 것이 좋다.
구조체 선언의 구문은 다음과 같다.
```c
struct 태그_이름 {
자료형 멤버1;
자료형 멤버2;
};
```
`태그_이름`은 생략 가능하다.
`typedef` 키워드를 사용하면 `struct` 키워드 없이 구조체 형식을 참조할 수 있다.
예시:
```c
typedef struct tag_name {
type member1;
type member2;
} thing_t;
thing_t thing;
```
C++ 코드에서는 `typedef`가 필요하지 않으며, `struct thing_t` 또는 `thing_t`로 형식을 참조할 수 있다.
간단한 C언어 구조체 예시:
```c
#include
/* PersonalData를 구조체로 정의 */
struct PersonalData
{
/* 멤버 변수 (즉, 구조체의 요소)를 이름과 나이로 한다 */
char Name[100];
int Age;
};
/* 위에서 정의된 구조체를 사용해 본다 */
int main(void)
{
struct PersonalData pd; /* 구조체 변수 선언 */
struct PersonalData *ppd; /* 구조체에 대한 포인터 */
scanf("%s", pd.Name); /* 값을 입력 */
scanf("%d", &(pd.Age)); /* 값을 입력 */
ppd = &pd;
ppd->Age++; /* 포인터가 가리키는 멤버에 접근하려면 화살표 연산자->를 사용한다. */
printf("%s-%d\n", pd.Name, pd.Age);
return 0;
}
```
C# 에서의 구조체 예시:
```csharp
using System;
public struct MutablePoint {
///
private int _x, _y;
///
public int X {
get => _x;
set => _x = value;
}
///
public int Y {
get => _y;
set => _y = value;
}
///
public double Distance => Math.Sqrt((double)_x * (double)_x + (double)_y * (double)_y);
///
public MutablePoint(int x, int y) {
_x = x;
_y = y;
}
///
public override string ToString() => $"({_x}, {_y})";
}
class Test {
// 필드로 정의된 구조체 내의 모든 필드는 기본값 0으로 초기화되어 있다.
static MutablePoint s_point;
static void Main() {
Console.WriteLine(s_point.ToString()); // (0, 0) 이 표시된다.
// 기본 생성자 호출。
var zeroPoint = new MutablePoint();
Console.WriteLine(zeroPoint.ToString()); // (0, 0) 이 표시된다.
// 인수가 있는 생성자 호출。
var point = new MutablePoint(5, 11);
Console.WriteLine(TranslatePoint(point, 1, -9).ToString()); // (6, 2) 가 표시된다.
Console.WriteLine(point.ToString()); // (5, 11) 가 표시된다.
TranslatePoint(ref point, -2, 3);
Console.WriteLine(point.ToString()); // (3, 14) 가 표시된다.
Console.WriteLine("Press any...");
Console.ReadKey(true);
}
// 구조체 형식 (값 형식) 은, 가인수에 값의 복사가 전달되므로,
// 메서드 내에서의 가인수 조작으로 호출원의 실인수의 값이 변경되는 일은 없다.
// 인수가 ref 파라미터라면,
// 또는 MutablePoint 형식이 struct 가 아니라 class 로 선언되어 있었다면,
// 가인수의 상태 변경은 실인수의 상태에 영향을 준다.
static MutablePoint TranslatePoint(MutablePoint p, int dx, int dy) {
p.X += dx;
p.Y += dy;
return p;
}
static void TranslatePoint(ref MutablePoint p, int dx, int dy) {
p.X += dx;
p.Y += dy;
}
}
3. 2. 구조체 변수 선언
`struct`는 선언부와 변수부로 나누어 선언할 수 있다. 선언부는 컴파일러에게 구조체의 구조를 알려주는 기능을 하며, `#include` 시 중복 선언이 가능하다. 선언부 만으로는 변수의 저장 공간을 확보하지 않기 때문이다.
```c
// 코드 파일 명 : man.h
#ifndef _MAN_H
#define _MAN_H
struct Man {
char name[50];
int age;
char gender;
char tel[50];
};
struct Man *getMan();
#endif
```
```c
// 코드 파일 명 : man.c
#include "man.h"
struct Man man;
struct Man *getMan() { return &man; }
char *getName() { return man.Name; }
int getAge() { return man.age; }
char getGender() { return man.sex; }
char *getTel() { return man.tel; }
```
```c
// 코드 파일 명 : main.c
#include
#include "man.h"
int main(int argc, char**argv)
{
struct Man *pman;
pman = getMan();
printf("Name=%s\n", pman->name);
return 0;
}
```
```c
struct Man {
char name[50];
int age;
char gender;
char tel[50];
} man;
```
`Man` 구조체를 여러 파일에서 사용한다면 구조체의 구조부를 include 해야 하는데, 그렇게 되면 `man` 변수가 중복되어 include를 한 번밖에 할 수 없다. 따라서 선언부만 분리해서 코딩하는 것이 좋다.
구조체 선언의 구문은 다음과 같다.
```c
struct 태그_이름 {
자료형 멤버1;
자료형 멤버2;
};
```
`태그_이름`은 생략 가능하다.
`typedef` 키워드를 사용하면 `struct` 키워드 없이 구조체 형식을 참조할 수 있지만, 일부 프로그래밍 스타일 가이드에서는 유형을 모호하게 만들 수 있다며 권장하지 않는다.
예를 들어:
```c
typedef struct tag_name {
type member1;
type member2;
} thing_t;
thing_t thing;
```
C++ 코드에서는 `struct`를 통해 정의된 형식이 일반적인 네임스페이스의 일부이므로 typedef가 필요하지 않으며, `struct thing_t` 또는 `thing_t`로 형식을 참조할 수 있다.
다음은 단순한 예시이다. (버퍼 오버플로우나 정수 오버플로우 등은 고려하지 않았다.)
```c
#include
/* PersonalData를 구조체로 정의 */
struct PersonalData
{
/* 멤버 변수 (즉, 구조체의 요소)를 이름과 나이로 한다 */
char Name[100];
int Age;
};
/* 위에서 정의된 구조체를 사용해 본다 */
int main(void)
{
struct PersonalData pd; /* 구조체 변수 선언 */
struct PersonalData *ppd; /* 구조체에 대한 포인터 */
scanf("%s", pd.Name); /* 값을 입력 */
scanf("%d", &(pd.Age)); /* 값을 입력 */
ppd = &pd;
ppd->Age++; /* 포인터가 가리키는 멤버에 접근하려면 화살표 연산자->를 사용한다. */
printf("%s-%d\n", pd.Name, pd.Age);
return 0;
}
```
다음은 구조체에 대한 포인터를 사용자 정의 객체 타입의 핸들로 사용하는 예이다.
```c
/* MyObject.h */
/* 구조체의 전방 선언 */
typedef struct MyObject MyObject;
extern MyObject* MyObject_create(void); /* 생성자 대체 */
extern void MyObject_destroy(MyObject* obj); /* 소멸자 대체 */
extern void MyObject_setPosition(MyObject* obj, double x, double y);
extern void MyObject_getPosition(const MyObject* obj, double* outX, double* outY);
```
```c
/* MyObject.c */
#include
#include
#include "MyObject.h"
/* 구조체의 정의 */
struct MyObject {
double x, y;
};
MyObject* MyObject_create(void) {
return calloc(1, sizeof(MyObject));
}
void MyObject_destroy(MyObject* obj) {
free(obj);
}
void MyObject_setPosition(MyObject* obj, double x, double y) {
assert(obj);
obj->x = x;
obj->y = y;
}
void MyObject_getPosition(const MyObject* obj, double* outX, double* outY) {
assert(obj && outX && outY);
}
```
현대적인 API 설계에서는 이처럼 C 호환 객체 지향 인터페이스를 정의하는 경우가 많다. 예를 들어, OpenCL[15]이나 Vulkan[16] 등에서 유사한 기법이 사용되고 있다. 헤더에서는 구조체의 전방 선언만 함으로써 구조체의 구체적인 세부 사항(정의)은 API 사용자에게 숨겨져 불투명한 타입(opaque type)으로 취급된다(캡슐화).
C#에서의 구조체는 소규모 데이터 구조 정의에 적합한 값 형식(value type)이다.[10] C#의 클래스는 힙 할당을 필요로 하고 완전한 상속 기능을 지원하는 참조 형식(reference type)인 반면[11], 구조체는 힙 할당을 필요로 하지 않는 가벼운 값 형식이며, 대신 파생 형식을 정의할 수 없는 등 제한된 클래스처럼 동작한다.
```csharp
// int 형의 숫자 리터럴도 구조체 (System.Int32) 의 인스턴스이며, 객체이다.
string str = 123.ToString();
```
`struct` 키워드를 사용한 사용자 정의 구조체의 예는 다음과 같다.
```csharp
using System;
public struct MutablePoint {
///
private int _x, _y;
///
public int X {
get => _x;
set => _x = value;
}
///
public int Y {
get => _y;
set => _y = value;
}
///
public double Distance => Math.Sqrt((double)_x * (double)_x + (double)_y * (double)_y);
///
public MutablePoint(int x, int y) {
_x = x;
_y = y;
}
///
public override string ToString() => $"({_x}, {_y})";
}
class Test {
// 필드로 정의된 구조체 내의 모든 필드는 기본값 0으로 초기화되어 있다.
static MutablePoint s_point;
static void Main() {
Console.WriteLine(s_point.ToString()); // (0, 0) 이 표시된다.
// 기본 생성자 호출。
var zeroPoint = new MutablePoint();
Console.WriteLine(zeroPoint.ToString()); // (0, 0) 이 표시된다.
// 인수가 있는 생성자 호출。
var point = new MutablePoint(5, 11);
Console.WriteLine(TranslatePoint(point, 1, -9).ToString()); // (6, 2) 가 표시된다.
Console.WriteLine(point.ToString()); // (5, 11) 가 표시된다.
TranslatePoint(ref point, -2, 3);
Console.WriteLine(point.ToString()); // (3, 14) 가 표시된다.
Console.WriteLine("Press any...");
Console.ReadKey(true);
}
// 구조체 형식 (값 형식) 은, 가인수에 값의 복사가 전달되므로,
// 메서드 내에서의 가인수 조작으로 호출원의 실인수의 값이 변경되는 일은 없다.
// 인수가 ref 파라미터라면,
// 또는 MutablePoint 형식이 struct 가 아니라 class 로 선언되어 있었다면,
// 가인수의 상태 변경은 실인수의 상태에 영향을 준다.
static MutablePoint TranslatePoint(MutablePoint p, int dx, int dy) {
p.X += dx;
p.Y += dy;
return p;
}
static void TranslatePoint(ref MutablePoint p, int dx, int dy) {
p.X += dx;
p.Y += dy;
}
}
3. 3. 구조체 멤버 접근
c
struct point {
int x;
int y;
} my_point;
struct point *p = &my_point; /* struct point 타입의 포인터 p를 선언 */
my_point.y = 3; /* struct의 두 번째 변수의 값을 변경 */
(*p).x = 8; /* struct의 첫 번째 멤버에 접근 */
p->x = 8; /* struct의 첫 번째 멤버에 접근하는 또 다른 방법 */
```
구조체 멤버 접근 시 포인터 표현 방법에는 `->`와 `.`이 있다.
포인터를 사용하여 구조체의 주소를 참조할 수 있다. 이는 구조체를 함수에 전달하여 구조체 복사 오버헤드를 피하는 데 유용하다. `->` 연산자는 역참조하여 포인터(왼쪽 피연산자)를 참조하고 구조체 멤버의 값(오른쪽 피연산자)에 접근한다.
```c
struct point_t point = { 3, 7 };
int x = point.x;
point.x = 10;
struct point_t *pp = &point;
x = pp->x;
pp->x = 8;
```
Below is a simple example, note that buffer overflow, integer overflow, etc. are not considered.영어
```c
#include
/* PersonalData를 구조체로 정의 */
struct PersonalData
{
/* 멤버 변수 (즉, 구조체의 요소)를 이름과 나이로 한다 */
char Name[100];
int Age;
};
/* 위에서 정의된 구조체를 사용해 본다 */
int main(void)
{
struct PersonalData pd; /* 구조체 변수 선언 */
struct PersonalData *ppd; /* 구조체에 대한 포인터 */
scanf("%s", pd.Name); /* 값을 입력 */
scanf("%d", &(pd.Age)); /* 값을 입력 */
ppd = &pd;
ppd->Age++; /* 포인터가 가리키는 멤버에 접근하려면 화살표 연산자->를 사용한다. */
printf("%s-%d\n", pd.Name, pd.Age);
return 0;
}
```
아래는 구조체에 대한 포인터를 사용자 정의 객체 타입의 핸들로 사용하는 예이다.
```c
/* MyObject.h */
/* 구조체의 전방 선언 */
typedef struct MyObject MyObject;
extern MyObject* MyObject_create(void); /* 생성자 대체 */
extern void MyObject_destroy(MyObject* obj); /* 소멸자 대체 */
extern void MyObject_setPosition(MyObject* obj, double x, double y);
extern void MyObject_getPosition(const MyObject* obj, double* outX, double* outY);
```
```c
/* MyObject.c */
#include
#include
#include "MyObject.h"
/* 구조체의 정의 */
struct MyObject {
double x, y;
};
MyObject* MyObject_create(void) {
return calloc(1, sizeof(MyObject));
}
void MyObject_destroy(MyObject* obj) {
free(obj);
}
void MyObject_setPosition(MyObject* obj, double x, double y) {
assert(obj);
obj->x = x;
obj->y = y;
}
void MyObject_getPosition(const MyObject* obj, double* outX, double* outY) {
assert(obj && outX && outY);
}
```
현대적인 API 설계에서는 이처럼 C 호환 객체 지향 인터페이스를 정의하는 경우가 많다. 예를 들어, OpenCL[15]이나 Vulkan[16] 등에서 유사한 기법이 실제로 사용되고 있다. 헤더에서는 구조체의 전방 선언만 함으로써 구조체의 구체적인 세부 사항(정의)은 API 사용자에게 숨겨져 불투명한 타입(opaque type)으로 취급된다(encapsulation영어).
이 기법을 사용하면 C 호환 인터페이스를 유지하면서 API 함수의 구현을 C뿐만 아니라 C++ 또는 다른 언어로 작성할 수도 있다. 또한, 객체의 생성 및 파괴를 포함한 모든 조작을 API 함수를 통해서만 제한하고, 객체 핸들을 통한 조작만을 제공함으로써 서로 다른 ABI 간에도 객체를 올바르게 주고받을 수 있으므로 C/C++ 이외의 다른 언어용 바인딩(래퍼 라이브러리)을 작성하는 것도 쉬워진다.
3. 4. 구조체 초기화
C89 스타일 초기화는 구조체의 멤버에 순서대로 값을 지정할 때 사용된다.[3] 예를 들어, 다음과 같이 `point_t` 구조체의 멤버 `x`와 `y`에 각각 1과 2를 할당할 수 있다.
```c
struct point_t {
int x;
int y;
};
struct point_t a = { 1, 2 };
```
지정된 초기화자를 사용하면 멤버의 순서를 바꾸거나, 특정 멤버만 초기화할 수 있다.[4] 예를 들어, 다음과 같이 `y` 멤버를 먼저 초기화하고, 그 다음에 `x` 멤버를 초기화할 수 있다.
```c
struct point_t a = { .y = 2, .x = 1 };
```
초기화되지 않은 멤버는 0으로 초기화된다. 이는 객체가 정적 메모리 할당 방식으로 할당된 경우에도 마찬가지이다.
또한, 같은 타입의 다른 구조체 변수로부터 값을 복사하여 초기화할 수도 있다. 예를 들어:
```c
struct point_t b = a;
4. 구조체의 메모리 배치
struct의 내부 변수는 선언된 순서대로 메모리에 배치된다. 8비트 CPU는 기본적으로 8비트 단위로 접근하므로 모든 변수를 빈 공간 없이 배치할 수 있다. 그러나 32비트 CPU는 접근 단위가 8, 16, 32비트로 다양하여 메모리 배치에 다른 고려가 필요하다.
예를 들어, 32비트 CPU에서 `int` 형 변수를 선언하면, CPU가 한 번에 접근할 수 있도록 컴파일러는 해당 변수의 주소를 4의 배수로 할당한다. 이는 CPU 주소 버스의 A1:A0=00b를 의미한다.
C#, VB.NET, F#과 같은 .NET 언어나 C++/CLI에서는 구조체를 통해 메모리 레이아웃을 명시적으로 지정할 수 있다. 이를 통해 Windows API 등에서 정의된 구조체와 호환되는 형식을 정의하여 P/Invoke에 사용할 수 있다.[12][19][20]
구조체 멤버의 메모리 레이아웃은 반드시 연속적이지 않으며, 프로세서 아키텍처에 따라 컴파일러가 접근 효율을 최적화하기 위해 바이트 경계에 따른 패딩(padding)을 삽입할 수 있다. 이 패딩은 직렬화(serialization)나 상호 운용 시 문제를 일으킬 수 있으므로, 정렬을 명시적으로 조정하는 기능이 제공되기도 한다.[21][22] 그러나 정렬되지 않은 주소에 멤버가 배치되면 버스 에러가 발생할 수 있다.
4. 1. 메모리 정렬 (Alignment)
32비트 CPU는 접근 단위가 8, 16, 32비트로 다양하지만, CPU의 효율적인 접근을 위해 컴파일러는 구조체 멤버들을 특정 바이트 경계에 맞춰 정렬할 수 있다. 예를 들어, 32비트 변수는 메모리 주소가 4의 배수가 되도록 배치되어 한 번에 접근할 수 있다. 이는 CPU 주소 버스의 A1:A0=00b를 의미한다.
다음 C 코드 예시를 보자.
struct Man {
int age;
char gender;
int id;
char name[11];
short int score;
};
struct Man man = { 19, 'M', 0x12345678, "Hong길동" , 100};
32비트 little-endian CPU에서 컴파일하면, `int` 멤버 사이에 있는 `char` 멤버는 4바이트 중 1바이트만 사용하고, 나머지 3바이트는 사용되지 않는다. 이는 다음 `int`형 멤버인 `id`가 32비트 단위로 접근되어야 하기 때문에, 빈 공간(패딩)을 넣어 배치한 것이다. 따라서 `sizeof(struct Man)`은 28바이트가 된다.
이처럼, 구조체의 멤버는 메모리에 연속적으로 배치되지 않을 수 있다. 컴파일러는 프로세서 아키텍처에 맞춰 접근 효율을 최적화하기 위해 바이트 경계에 따른 패딩(padding)을 삽입할 수 있다. 이러한 패딩은 직렬화(serialization)나 상호 운용 시 문제를 일으킬 수 있어, 정렬을 명시적으로 조정하는 기능이 제공되기도 한다.[21][22] 그러나 정렬되지 않은 주소에 멤버가 배치되면 버스 에러가 발생할 수 있다.
4. 2. Little-endian과 Big-endian
32비트 CPU에서 구조체(struct)를 사용할 때, 메모리 접근 효율을 높이기 위해 변수들을 특정 바이트 경계에 맞춰 배치한다. 예를 들어, 32비트 정수형 변수 `int`는 메모리 주소가 4의 배수가 되도록 배치하여 한 번에 접근할 수 있도록 한다.다음 C 코드를 보자.
```c
struct Man {
int age;
char gender;
int id;
char name[11];
short int score;
};
struct Man man = { 19, 'M', 0x12345678, "Hong길동" , 100};
```
32비트 little-endian CPU에서 컴파일하면, `struct Man`의 멤버들은 위 그림과 같이 배치된다. `int` 형 변수 사이에 `char` 형 변수가 있으면, 4바이트 중 1바이트만 사용하고 나머지 3바이트는 사용하지 않는다. 이는 다음 `int` 형 변수 `id`가 32비트 단위로 접근되어야 하기 때문에 빈 공간(padding bit)을 넣어 배치한 것이다. 따라서 `sizeof(struct Man)`은 28바이트가 된다.
구조체 멤버의 메모리 레이아웃은 항상 연속적이지 않다. 컴파일러는 실행 환경(프로세서 아키텍처)에 맞춰 접근 효율을 최적화하기 위해 바이트 경계에 따라 채움(패딩)을 삽입할 수 있다.[21][22]
5. 구조체의 활용
구조체는 여러 프로그래밍 언어에서 데이터를 묶어 관리하는 데 사용되며, 특히 객체 지향 프로그래밍에서 중요한 역할을 한다.
C/C++, C#, .NET 등 다양한 환경에서 구조체가 활용되는 방식에는 차이가 있다. C/C++에서는 클래스와 유사하지만 접근 제어자의 기본값이 다르다. C#과 .NET에서는 값 형식으로 동작하여 스택에 할당되므로 가볍고 빠른 데이터 구조를 제공한다.
현대적인 API 설계에서는 구조체를 불투명 타입(opaque type)으로 사용하여 캡슐화를 지원하고, C 호환 객체 지향 인터페이스를 정의하는 데 활용된다. OpenCL[15]이나 Vulkan[16] 등이 이러한 예시이다.
5. 1. C/C++에서의 구조체
C++에서 구조체(struct)는 클래스와 동일하지만, 다른 기본 가시성을 갖는다. 클래스 멤버는 기본적으로 private이고, 구조체 멤버는 기본적으로 public이다.[15][16]아래는 C/C++ 구조체를 활용하는 예시이다.
- C 스타일 구조체 예시
```c
#include
/* PersonalData를 구조체로 정의 */
struct PersonalData
{
/* 멤버 변수 (즉, 구조체의 요소)를 이름과 나이로 한다 */
char Name[100];
int Age;
};
/* 위에서 정의된 구조체를 사용해 본다 */
int main(void)
{
struct PersonalData pd; /* 구조체 변수 선언 */
struct PersonalData *ppd; /* 구조체에 대한 포인터 */
scanf("%s", pd.Name); /* 값을 입력 */
scanf("%d", &(pd.Age)); /* 값을 입력 */
ppd = &pd;
ppd->Age++; /* 포인터가 가리키는 멤버에 접근하려면 화살표 연산자->를 사용한다. */
printf("%s-%d\n", pd.Name, pd.Age);
return 0;
}
```
- 사용자 정의 객체 타입의 핸들로 구조체 포인터를 사용하는 예시
```c
/* MyObject.h */
/* 구조체의 전방 선언 */
typedef struct MyObject MyObject;
extern MyObject* MyObject_create(void); /* 생성자 대체 */
extern void MyObject_destroy(MyObject* obj); /* 소멸자 대체 */
extern void MyObject_setPosition(MyObject* obj, double x, double y);
extern void MyObject_getPosition(const MyObject* obj, double* outX, double* outY);
```
```c
/* MyObject.c */
#include
#include
#include "MyObject.h"
/* 구조체의 정의 */
struct MyObject {
double x, y;
};
MyObject* MyObject_create(void) {
return calloc(1, sizeof(MyObject));
}
void MyObject_destroy(MyObject* obj) {
free(obj);
}
void MyObject_setPosition(MyObject* obj, double x, double y) {
assert(obj);
obj->x = x;
obj->y = y;
}
void MyObject_getPosition(const MyObject* obj, double* outX, double* outY) {
assert(obj && outX && outY);
- outX = obj->x;
- outY = obj->y;
}
```
현대적인 API 설계에서는 이처럼 C 호환 객체 지향 인터페이스를 정의하는 경우가 많다. 예를 들어, OpenCL[15]이나 Vulkan[16] 등에서 유사한 기법이 실제로 사용되고 있다. 헤더에서는 구조체의 전방 선언만 함으로써 구조체의 구체적인 세부 사항(정의)은 API 사용자에게 숨겨져 불투명한 타입(opaque type)으로 취급된다(캡슐화).
5. 2. C#에서의 구조체
C#에서 `struct`는 값 형식으로, C#에서 소규모 데이터 구조를 정의하는 데 적합하다.[10] 구조체는 스택에 할당되어 힙 할당을 필요로 하지 않으며, 가볍고 빠른 데이터 구조를 제공한다. 반면, C#의 클래스는 참조 형식으로 힙 할당이 필요하며, 상속 등 더 많은 기능을 제공한다.[11]내장된 가벼운 형식(`bool`, `char`, `int`, `double` 등)도 구조체이다. 예를 들어, `int` 형의 숫자 리터럴도 `System.Int32` 구조체의 인스턴스이며 객체이다.
```csharp
// int 형의 숫자 리터럴도 구조체 (System.Int32) 의 인스턴스이며, 객체이다.
string str = 123.ToString();
```
`struct` 키워드를 사용한 사용자 정의 구조체의 예시는 다음과 같다.
```csharp
using System;
public struct MutablePoint {
///
private int _x, _y;
///
public int X {
get => _x;
set => _x = value;
}
///
public int Y {
get => _y;
set => _y = value;
}
///
public double Distance => Math.Sqrt((double)_x * (double)_x + (double)_y * (double)_y);
///
public MutablePoint(int x, int y) {
_x = x;
_y = y;
}
///
public override string ToString() => $"({_x}, {_y})";
}
```
C#의 구조체는 인수를 갖는 생성자를 사용자 정의할 수 있지만, 인수가 없는 생성자(기본 생성자)는 사용자 정의할 수 없었다. 또한 필드나 프로퍼티를 선언 시 초기화할 수도 없었다. 인수가 없는 생성자에서는 각 필드는 해당 형식의 기본값으로 초기화된다. 그러나 C# 10.0 이후부터는 구조체도 인수가 없는 생성자를 사용자 정의할 수 있게 되었다.[18]
구조체는 값 형식으로 작동하기 때문에 구조체를 함수에 전달할 때 값이 복사된다. 따라서 입력 매개변수의 변경 사항이 전달된 값에 영향을 미치지 않는다.
C#의 구조체는 속성을 사용하여 메모리 레이아웃을 명시적으로 지정할 수 있어, C/C++ 구조체와의 상호 운용에 편리하다. Windows API 등에서 정의된 구조체와 호환되는 형식을 C# 구조체를 통해 사용자 정의함으로써 P/Invoke에 이용할 수 있다.
VB.NET이나 F#과 같은 다른 .NET 언어들도 C#과 유사한 구조체를 가지고 있다.[12][19] C++/CLI에서는 `value struct` 또는 `value class`를 통해 .NET의 구조체 형식을 정의할 수 있다.[20]
5. 3. .NET 언어에서의 구조체
.NET 언어(C#, VB.NET 등)에서 구조체는 값 형식으로 동작하며, 클래스와 유사한 기능을 제공하지만, 참조 형식은 아니라는 차이점이 있다.[10] 예를 들어, .NET 구조체를 함수에 전달할 때 값이 복사되므로 입력 매개변수의 변경 사항이 전달된 값에 영향을 미치지 않는다.C#에서 구조체는 소규모 데이터 구조 정의에 적합하다.[10] C#의 클래스는 힙 할당을 필요로 하고 완전한 상속 기능을 지원하는 참조 형식인 반면,[11] 구조체는 힙 할당을 필요로 하지 않는 가벼운 값 형식이며, 파생 형식을 정의할 수 없는 등 제한된 클래스처럼 동작한다. 클래스와 구조체의 사용법에 대해서는 가이드라인이 제시되어 있다.[17]
내장된 가벼운 형식(
bool
, char
, int
, double
, etc.)도 구조체이다.```csharp
// int 형의 숫자 리터럴도 구조체 (System.Int32) 의 인스턴스이며, 객체이다.
string str = 123.ToString();
```
`struct` 키워드를 사용한 사용자 정의 구조체의 예시는 다음과 같다.
```csharp
using System;
public struct MutablePoint {
///
private int _x, _y;
///
public int X {
get => _x;
set => _x = value;
}
///
public int Y {
get => _y;
set => _y = value;
}
///
public double Distance => Math.Sqrt((double)_x * (double)_x + (double)_y * (double)_y);
///
public MutablePoint(int x, int y) {
_x = x;
_y = y;
}
///
public override string ToString() => $"({_x}, {_y})";
}
class Test {
// 필드로 정의된 구조체 내의 모든 필드는 기본값 0으로 초기화되어 있다.
static MutablePoint s_point;
static void Main() {
Console.WriteLine(s_point.ToString()); // (0, 0) 이 표시된다.
// 기본 생성자 호출。
var zeroPoint = new MutablePoint();
Console.WriteLine(zeroPoint.ToString()); // (0, 0) 이 표시된다.
// 인수가 있는 생성자 호출。
var point = new MutablePoint(5, 11);
Console.WriteLine(TranslatePoint(point, 1, -9).ToString()); // (6, 2) 가 표시된다.
Console.WriteLine(point.ToString()); // (5, 11) 가 표시된다.
TranslatePoint(ref point, -2, 3);
Console.WriteLine(point.ToString()); // (3, 14) 가 표시된다.
Console.WriteLine("Press any...");
Console.ReadKey(true);
}
// 구조체 형식 (값 형식) 은, 가인수에 값의 복사가 전달되므로,
// 메서드 내에서의 가인수 조작으로 호출원의 실인수의 값이 변경되는 일은 없다.
// 인수가 ref 파라미터라면,
// 또는 MutablePoint 형식이 struct 가 아니라 class 로 선언되어 있었다면,
// 가인수의 상태 변경은 실인수의 상태에 영향을 준다.
static MutablePoint TranslatePoint(MutablePoint p, int dx, int dy) {
p.X += dx;
p.Y += dy;
return p;
}
static void TranslatePoint(ref MutablePoint p, int dx, int dy) {
p.X += dx;
p.Y += dy;
}
}
```
C#의 구조체는 속성을 사용하여 메모리 레이아웃을 명시적으로 지정할 수 있으므로 C/C++의 구조체와의 상호 운용에 편리하다. Windows API 등에서 정의된 구조체와 호환되는 형식을 C#의 구조체를 통해 사용자 정의함으로써 P/Invoke에 이용할 수 있다.
VB.NET이나 F#과 같은 .NET 언어도, 구문은 다르지만 C#과 유사한 구조체를 갖추고 있다.[12][19]
C++/CLI에서는, `value struct` 또는 `value class` 를 통해 .NET의 구조체 형식을 정의할 수 있다.[20]
5. 4. API 설계에서의 구조체 활용
현대적인 API 설계에서 구조체는 불투명 타입(opaque type)으로 사용되어 캡슐화를 지원하고, C 호환 객체 지향 인터페이스를 정의하는 데 활용된다. OpenCL[15]이나 Vulkan[16] 등에서 이와 유사한 기법이 실제로 사용되고 있다. 헤더에서는 구조체의 전방 선언만 함으로써 구조체의 구체적인 세부 사항(정의)은 API 사용자에게 숨겨져 불투명한 타입으로 취급된다.이 기법을 사용하면 C 호환 인터페이스를 유지하면서 API 함수의 구현을 C뿐만 아니라 C++ 또는 다른 언어로 작성할 수 있다. 또한, 객체의 생성 및 파괴를 포함한 모든 조작을 API 함수를 통해서만 제한하고, 객체 핸들을 통한 조작만을 제공함으로써 서로 다른 ABI 간에도 객체를 올바르게 주고받을 수 있으므로 C/C++ 이외의 다른 언어용 바인딩(래퍼 라이브러리)을 작성하는 것도 쉬워진다.
6. 한국 정보 기술 산업과 구조체
한국 IT 산업은 빠르게 성장하면서 효율적인 데이터 관리가 중요해졌고, 구조체(struct)는 이러한 데이터 관리의 핵심 기술 중 하나로 사용되고 있다.
6. 1. 임베디드 시스템
32비트 CPU는 메모리 접근 단위가 기계어에 따라 8, 16, 32비트로 다양하다. 예를 들어, 정수형 변수 `int`를 선언하면 CPU는 한 번에 접근할 수 있도록 기계어로 컴파일한다. 따라서 32비트 변수는 메모리 주소값이 4의 배수로 할당되어야 한 번에 접근할 수 있다.
다음 C 언어 코드 예시를 보자.
struct Man {
int age;
char gender;
int id;
char name[11];
short int score;
};
struct Man man = { 19, 'M', 0x12345678, "Hong길동" , 100};
일반적으로 32비트 little-endian CPU로 컴파일하면, `struct` 멤버의 메모리 배치는 위 그림과 같이 된다. 정수형 `int` 사이에 `char` 변수는 4바이트 중 1바이트만 사용하고 나머지 3바이트는 사용하지 않는다. 이는 `id` 변수가 32비트 단위로 접근되어야 하므로 빈 공간을 넣는 방식으로 배치했기 때문이다. 이 `struct`의 크기는 빈 공간(padding bit)까지 포함하여 `sizeof(struct Man) == 28`바이트가 된다.
struct영어 멤버의 메모리 레이아웃은 반드시 연속적이지 않다. 실행 환경(프로세서 아키텍처)에 맞춰 접근 효율이 최적화되도록 컴파일러가 바이트 경계에 따른 익명의 채움(패딩)을 삽입할 수 있다. 이 패딩은 직렬화나 상호 운용 등에서 문제가 될 수 있으므로, 필드 속성이나 컴파일러 고유의 지시자를 통해 정렬을 명시적으로 조정할 수 있는 언어 및 처리 시스템도 존재한다.[21][22] 단, 정렬되지 않은 주소에 구조체 멤버가 배치된 경우 멤버 접근이 버스 에러를 일으키는 경우도 있다.
참조
[1]
웹사이트
Struct memory layout in C
https://stackoverflo[...]
[2]
논문
The Development of the C Language
http://www.bell-labs[...]
1993-03
[3]
서적
A Book On C: Programming in C
https://archive.org/[...]
[4]
웹사이트
IBM Linux compilers. Initialization of structures and unions
https://www.ibm.com/[...]
[5]
웹사이트
Parameter passing in C#
http://yoda.arachsys[...]
[6]
문서
User-defined data type (VBA)
https://docs.microso[...]
Microsoft Docs
[7]
문서
レコード・クラス
https://docs.oracle.[...]
Oracle Java SE 16 Help Center
[8]
문서
Java 16の最新情報
https://www.infoq.co[...]
InfoQ
[9]
문서
データクラス - Kotlin Programming Language
https://dogwood008.g[...]
[10]
문서
構造体型 - C# リファレンス
https://docs.microso[...]
Microsoft Docs
[11]
문서
クラス - C# プログラミング ガイド
https://docs.microso[...]
Microsoft Docs
[12]
문서
Structure ステートメント - Visual Basic
https://docs.microso[...]
Microsoft Docs
[13]
문서
構造体とクラス(Structures and Classes) · The Swift Programming Language日本語版
https://www.swiftlan[...]
[14]
문서
Structures and Classes — The Swift Programming Language (Swift 5.6)
https://docs.swift.o[...]
[15]
문서
OpenCL-Headers/cl.h at master · KhronosGroup/OpenCL-Headers
https://github.com/K[...]
[16]
문서
Vulkan-Headers/vulkan_core.h at master · KhronosGroup/Vulkan-Headers
https://github.com/K[...]
[17]
문서
Choosing Between Class and Struct - Framework Design Guidelines
https://docs.microso[...]
Microsoft Docs
[18]
문서
Parameterless struct constructors - C# 10.0 draft specifications
https://learn.micros[...]
Microsoft Learn
[19]
문서
Structures - F#
https://docs.microso[...]
Microsoft Docs
[20]
문서
ref class and ref struct (C++/CLI and C++/CX)
https://docs.microso[...]
Microsoft Docs
[21]
문서
StructLayoutAttribute Class (System.Runtime.InteropServices)
https://docs.microso[...]
Microsoft Docs
[22]
문서
Storage and Alignment of Structures
https://docs.microso[...]
Microsoft Docs
본 사이트는 AI가 위키백과와 뉴스 기사,정부 간행물,학술 논문등을 바탕으로 정보를 가공하여 제공하는 백과사전형 서비스입니다.
모든 문서는 AI에 의해 자동 생성되며, CC BY-SA 4.0 라이선스에 따라 이용할 수 있습니다.
하지만, 위키백과나 뉴스 기사 자체에 오류, 부정확한 정보, 또는 가짜 뉴스가 포함될 수 있으며, AI는 이러한 내용을 완벽하게 걸러내지 못할 수 있습니다.
따라서 제공되는 정보에 일부 오류나 편향이 있을 수 있으므로, 중요한 정보는 반드시 다른 출처를 통해 교차 검증하시기 바랍니다.
문의하기 : help@durumis.com