1. Home
  2. 프로그래밍 강좌/C
  3. [C언어 강좌] #14-1 구조체(Structure Type)

[C언어 강좌] #14-1 구조체(Structure Type)

https://www.geeksforgeeks.org/structures-c/

안녕하세요 파일입니다. 드디어 지겹던 포인터가 끝나고 새로운 강좌명으로 상쾌한 시작을 할 수 있게 되었습니다 !

 

* 하지만 포인터는 포인터가 끝난뒤에도 항상 어디서나 등장합니다.. 여기서도 나와요 

 

오늘 배워볼것은 구조체(Structure Type)입니다.

 

만약에 '학생1' 이 있고 이름이 '홍길동' 이라면 이것을 프로그래밍으로 표현할때 어떻게 하면 좋을까요?

#include <stdio.h>

int main(){
	char * student1 = "홍길동";
	printf("학생 1의 이름은 : %s 입니다.", student1);
	return 0;
}

저번에 배운 포인터 문자열을 활용하면, 대충 이렇게 하면 되겠죠?

근데 학생을 관리하려면 이름만 알아선 부족합니다. 예를 들어서 학번도 있을꺼고, 고등학생이라면 학년, 반도 있겠네요. 특징은 '학생' 이라는 것인데 저장해야 할것이 이름, 학번, 학년, 반.. 대충 생각해도 4가지가 됩니다.

 

물론 이름,학번,학년,반 을 문자열 변수로 4개씩 만들어 낼 수는 있습니다만 묶어서 관리할 수 있도록

그룹화 할 수는 없는걸까요?

 

지금까지 배운것으론 표현을 할 수가 없네요. 그러나 구조체를 배우면 이런것을 할 수 있게 됩니다.

구조체는 사용자가 정의하여 사용하는 사용자 정의 자료형으로 하나 이상의 변수들을 그룹화하여 새로운 자료형을 만들게 됩니다.

 

구조체에서 필요한 정보는 아래와 같습니다.

1. 구조체 이름 (새로운 자료형이므로 이 경우엔 '학생')

2. 구조체 멤버 변수 (이름, 학번, 학년, 반)

3. 구조체 변수 (학생1)

 

객체지향 언어를 배워보셨으면 C의 구조체는 일종의 class 느낌이라고 보시면 됩니다.

(모르는 내용이면 그냥 무시하고 넘어가주세요)

 

구조체

구조체란 하나 이상의 변수를 묶어 그룹화하는 사용자 정의 자료형입니다.

그룹화할 때 같은 자료형을 가진 변수들을 묶어 그룹화할 수 있고, 서로 다른 자료형을 가진 변수들을 묶어서 그룹화할 수도 있습니다.

 

 

구조체의 정의는 위와 같이 합니다.

구조체를 정의한다는 의미는 구조체를 만든다는 의미와 같습니다.

 

기본적으로 struct 키워드를 이용하여 구조체를 정의하며, 구조체 이름을 적고

중괄호로 묶어서 멤버 변수를 적어주면 됩니다.

 

중괄호로 여러개를 적어준다는 점에선 배열과 비슷해보이지만

주의할점은 구조체 멤버 변수는 콤마로 구분하지 않고 세미콜론으로 구분을 합니다.

그리고 마지막에 세미콜론을 찍어줌으로써 구조체 정의가 종료됩니다.

 

위 선언을 정리해보자면

point라는 구조체를 정의한다.

point라는 구조체 내부 멤버 변수(구성원들)은 각각 int x, int y이다.

 

로 정리해볼 수 있겠습니다.

 

이렇게 정의된 구조체를 우리는 사용자 정의 자료형이라고 합니다.

 

구조체 활용

구조체는 기본적으로 사용자 정의 자료형을 만들어서 그룹화 하는 개념이므로

프로그래밍에서 매우 유용하게 사용할 수 있습니다. 아래는 그 예시입니다.

(자세한 접근 방법이나 사용방법은 아래에서 배웁니다.)

//좌표 평면을 구조화
struct pos
{
	int x;
	int y;
};
//학생을 구조화
struct student
{
	char * name; //이름
	char * id; //학번
	int grade; //학년
	int class; //반
};
//벡터를 구조화  
struct vector
{
	int n1; //벡터 성분 1 
	int n2; //벡터 성분 2 
	int dir; //방향 
};

구조체 변수

구조체를 정의만 해서는 의미가 없습니다.

구조체를 사용할 수 있도록 구조체 변수를 만들어줘야 하는데요.

 

구조체 변수란 구조체 멤버 변수에 접근하게 해주는 변수를 말합니다.

 

아래 예제는 구조체 변수를 선언하는 두가지 방법입니다.

 

struct point{
		int x;
		int y;
	};

struct point p1, p2, p3; //p1,p2,p3라는 구조체 변수 선언
struct point{
		int x;
		int y;
	} p1,p2, p3; //point라는 구조체를 정의함과 동시에 구조체 변수 선언

구조체를 정의해주고 나중에 구조체 변수를 만들어주는 방법,

아니면 정의와 동시에 변수를 만들어주는 방법 총 2가지가 있습니다.

 

상황, 필요에 따라서 사용해주시면 되겠습니다.

 

지금까지 일반 변수의 선언이 int a;와 같은 형태, 구조체 변수의 선언은 struct point a; 와 같은형태로

앞의 자료형이 달라졌을뿐 표현은 동일하게 하는걸 알 수 있습니다.

 

구조체 변수로 멤버 변수에 접근하기

구조체를 단순히 정의만해서는 쓸모가 없어서

구조체 변수를 만들어 줬습니다.

 

그런데 구조체 변수를 만들어주기만 하고 사용을 하지 않으면 당연히 의미가 없겠죠?

실제로 구조체 변수를 사용하는 방법 즉, 구조체 변수로 멤버 변수에 접근하는 방법을 알아봅시다.

 

#include <stdio.h>

struct point{
	int x;
	int y;
};
	
int main(){
	struct point pos;
	pos.x = 10;
	pos.y = 20;
	
	printf("현재 좌표는 : (%d, %d)", pos.x, pos.y);
	return 0;
}

현재 좌표는 : (10, 20)
--------------------------------
Process exited after 0.0141 seconds with return value 0
계속하려면 아무 키나 누르십시오 . . .

point라는 구조체의 구조체 변수인 pos를 만들어줬습니다.

pos.x, pos.y로 각각 멤버 변수 x,y에 접근해서 데이터를 저장하고

출력하는 예제입니다.

 

그림으로 나타내면 다음과 같습니다.

구조체 변수 여러개를 만든다면 위와 같은 초록색 네모박스가 여러개씩 만들어져서

데이터가 각각 관리된다고 보시면 됩니다.

 

위 예제의 경우에는 pos라는 구조체 변수를 선언해주고 pos.x , pos.y에 값을 넣어서 각각 값을 초기화 해주었지만

선언과 동시에 초기화 시키는 방법도 있습니다.

 

#include <stdio.h>

struct point{
	int x;
	int y;
};
	
int main(){
	struct point pos = {10,20};
	
	printf("현재 좌표는 : (%d, %d)", pos.x, pos.y);
	return 0;
}

간단하게 이런식으로 해주시면 됩니다.

중괄호로 묶고 순서대로 넣어주시면 돼요.

방금 위에서 봤던 표현과 동일한 표현입니다.

 

10,20 이면 pos.x , pos.y 정의된 순서대로 값이 알아서 들어가서 초기화 됩니다.

 

초기화란 단어 기억나시죠?

선언은 변수를 작성해서 만들어주는거고,

초기화란 변수의 값을 지정해주는 것을 의미합니다. (#3편)

 

위 소스코드 2개를 보시면 알겠지만 구조체 변수로 멤버변수에 접근할때는

 

구조체변수명.멤버변수; 형태로 써주시면 됩니다. --> ex) pos.x;저기서 .(점)은 접근 연산자로 구조체 변수로 멤버 변수에 접근하기 위해 쓰는 연산자 입니다.

 

#include <stdio.h>

struct point{
	int x;
	int y;
};
	
int main(){
	struct point pos;
	pos = {10,20};
	printf("현재 좌표는 : (%d, %d)", pos.x, pos.y);
	return 0;
}

그리고 주의할점은 위와 같이 pos를 선언 한 다음에 나중에 pos = {10,20} 으로 초기화 하는건 안됩니다.

무조건 struct point pos = {10,20}; 로 한줄에 써주셔야 합니다.

 

구조체 변수의 복사 및 비교

#include <stdio.h>

struct point{
	int x;
	int y;
};
	
int main(){
	struct point pos1 = {10,20};
	struct point pos2 = {20,40};
	
	printf("pos2 : %d %d\n", pos2.x, pos2.y);
	
	pos2 = pos1; //pos2에 pos1 복사 
	printf("after copy...\n");
	
	printf("pos2 : %d %d\n", pos2.x, pos2.y);
	
	return 0;
}

pos2 : 20 40
after copy...
pos2 : 10 20

--------------------------------
Process exited after 0.00978 seconds with return value 0
계속하려면 아무 키나 누르십시오 . . .

구조체 변수는 대입 연산자(=)로 복사가 가능합니다.

사칙연산은 불가능 하고 오로지 대입 연산만 가능합니다.

 

그리고 복사할때도 조건이 있는데 같은 구조체 변수끼리만 됩니다.

구조체가 달라버리면 형태가 같아도 복사가 안됩니다.

 

보시면 point2로 구조체를 하나 더 만들어서 point랑 멤버 변수 형태는 같은데 (둘다 같은 int형 변수, 이름도 똑같음)

pos1, pos2 끼리 구조체가 point / point2로 달라서 복사가 안됩니다.

 

아래 메세지에 보시면 incompatible types ~~ 라고 뜨죠?

호환되지 않는다는 의미입니다.

 

 

#include <stdio.h>

struct point{
	int x;
	int y;
};


int main(){
	struct point pos1 = {10,20};
	struct point pos2 = {20,40};
	
	if(pos1 == pos2){ //오류 발생 
		printf("same");
	}
	
	return 0;
}

또한 구조체 변수끼리 == 연산자로 같은지 비교하는것도 불가합니다.

 

이유를 추측해보자면 비교 기준이 없어서 그런거 같습니다.

위에선 int x가 같은지 비교하는지, int y가 같아야 하는지, 전부 같아야 하는지 이런 비교 기준이 없기 때문입니다.

 

그렇기에 >, < 같은 연산자도 안될것 입니다.

#include <stdio.h>

struct point{
	int x;
	int y;
};


int main(){
	struct point pos1 = {10,20};
	struct point pos2 = {20,40};
	
	if(pos1.y == pos2.x){
		printf("same");
	}
	
	return 0;
}

그래서 같은지 비교하고 싶으면

위처럼 멤버 변수로 접근을 해서 비교를 해야 합니다.

 

그렇게 어려운 내용은 아닐것이니 넘어가겠습니다.

 

중첩 구조체

구조체 내에 구조체를 넣어볼 수 있습니다.

중첩 구조체는 구조체 내에 구조체가 포함되어 있다는 의미입니다.

 

즉 박스안에 박스인 셈이죠.

 

#include <stdio.h>

struct class{
	int grade; //학년
	int class; //반 
};

struct student{
	char * name;
	struct class s; //구조체 변수 s를 구조체 student의 멤버 변수로 사용 (=중첩 구조체) 
};


int main(){
	struct student s1;
	s1.name = "홍길동";
	s1.s.grade = 2; //2학년 
	s1.s.class = 3;  //3반 
	
	printf("%s은 %d학년 %d반 입니다.", s1.name, s1.s.grade, s1.s.class);
	
	return 0;
}

다음 예제는 중첩 구조체를 사용한 예제입니다.

보시면 student라는 구조체 멤버 변수에 구조체 변수 s가 들어가 있는데 class라는 구조체에 의해 정의되어 있습니다.

 

보시면 class라는 구조체는 몇학년 몇반 (int grade, int class)로 되어 있죠.

즉 학년반도 구조체 묶어서 관리하고 싶어서 구조체 내부에 또다른 구조체를 쓴것 입니다.

물론 이렇게 할필요 없이 student에 다 넣어서 관리해도 되지만 예제를 보여드려야 하므로 이렇게 만들었습니다.

 

접근은 예제를 보고 익히시면 되겠는데요.

s1이라는 구조체 변수를 만들고 거기에 학년, 반을 저장하기 위해선 student에서 또 s라는 구조체 변수(학년, 반)에 접근해야 하므로

 

s1.s.grade = ... 와 같은 형태로 .을 하나씩 써주면서 접근하면 됩니다.

아마 이렇게 말로 설명하면 더 복잡할 겁니다.

 

그냥 소스코드를 보고 이렇게 쓰는구나 하고 익혀주시면 됩니다.

 

#include <stdio.h>

struct class{
	int grade; //학년
	int class; //반 
};

struct student{
	char * name;
	struct class s; //구조체 변수 s를 구조체 student의 멤버 변수로 사용 (=중첩 구조체) 
};


int main(){
	struct student s1 = {"홍길동", {2, 3}};
	
	printf("%s은 %d학년 %d반 입니다.", s1.name, s1.s.grade, s1.s.class);
	
	return 0;
}

이렇게 아까처럼 한줄짜리로 선언과 동시에 초기화가 가능합니다.

구조체로 묶이는 부분만 중괄호로 묶어서 콤마로 구분해주시면 되겠습니다.

 

"홍길동" 자리는 student.name 자리이고

{2,3} 이라고 적힌 자리는 student.s1.grade, student.s1.class 가 되게 됩니다.

s1 이라는 부분에서 구조체로 한번 묶이므로 중괄호가 들어간 것입니다.

 

#include <stdio.h>

struct class{
	int grade; //학년
	int class; //반 
};

struct student{
	char * name;
	struct class s; //구조체 변수 s를 구조체 student의 멤버 변수로 사용 (=중첩 구조체) 
};


int main(){
	struct student s1 = {"홍길동", 2, 3};
	
	printf("%s은 %d학년 %d반 입니다.", s1.name, s1.s.grade, s1.s.class);
	
	return 0;
}

그런데 사실 위처럼 중괄호를 생략해도 데이터는 순서대로 들어갑니다.

컴퓨터는 그저 순차적으로 선언대로 값을 하나하나 넣습니다.

 

어떻게 써주든 상관은 없으나 사람이 보는 입장에선 중괄호를 써주는게 가독성 측면에서 더 나아보입니다.

 

typedef를 이용한 구조체의 재정의

자료형 편에서 typedef라는 것을 간략하게 설명하고 넘어갔는데요.

그렇게 어려운 내용도 아니라서 자세히 설명하진 않았지만

typedef를 구조체에서 사용하면 노가다를 줄일 수 있어서 꽤 유용합니다.

 

한번 복습해보겠습니다.

 

#include <stdio.h>

typedef int int32;
	
int main(){
	int32 a = 10; //int a = 10과 동일한 표현 
	return 0;
}

typedef 기존 자료형 새 이름;

로 써주시면 사용자 정의 자료형을 만들 수 있습니다.

 

typedef int int32;

로 써주시면 변수를 만들때 int 대신에 int32 이라고 써줘도 똑같은 표현이 되게 됩니다.

 

쉽게 말해서 기존 자료형의 이름을 그냥 바꿔서 쓸 수 있다 입니다.

구조체에서 이를 활용해봅시다.

 

#include <stdio.h>

struct student
{
	char * name;
	int code; //학번 
	int grade; //학년 
	int class; //반 
};

typedef struct student Student; //student 구조체를 Student로 쓴다. 

int main(){
	Student stu = {"김철수", 20210000, 1, 1}; //struct student stu = {20210000, 1, 1} 과 동일한 표현임. 
	printf("%s님의 학번은 %d 입니다. %d학년 %d반입니다.", stu.name, stu.code, stu.grade, stu.class);
}

김철수님의 학번은 20210000 입니다. 1학년 1반입니다.
--------------------------------
Process exited after 0.007967 seconds with return value 51
계속하려면 아무 키나 누르십시오 . . .

다음 예제를 보시면 typedef 로 struct student 를 Student로 재정의하고 있습니다.

Student stu는 struct student stu 와 동일한 표현입니다.

 

좀더 코드를 줄이고 싶다면 구조체를 선언함과 동시에 typedef로 재정의하는 방법도 있습니다

아래처럼 해주시면 됩니다.

#include <stdio.h>

typedef struct student
{
	char * name;
	int code; //학번 
	int grade; //학년 
	int class; //반 
	
} Student; //student 구조체를 Student로 쓴다. 

int main(){
	Student stu = {"김철수", 20210000, 1, 1}; //struct student stu = {20210000, 1, 1} 과 동일한 표현임. 
	printf("%s님의 학번은 %d 입니다. %d학년 %d반입니다.", stu.name, stu.code, stu.grade, stu.class);
}

김철수님의 학번은 20210000 입니다. 1학년 1반입니다.
--------------------------------
Process exited after 0.006448 seconds with return value 51
계속하려면 아무 키나 누르십시오 . . .

보시면 typedef안 struct student 자리에 student 구조체 선언부분을 그대로 넣었습니다.

저렇게 쓰시면 구조체 정의와 동시에 typedef 선언이 가능해집니다.

 

또한 저렇게 정의를 하면 대부분은 struct student가 아닌 Student로 쓸려고 한 것이므로

student라는 이름은 그렇게 중요해지지 않습니다.

 

즉 아래처럼 생략이 가능합니다.

#include <stdio.h>

typedef struct
{
	char * name;
	int code; //학번 
	int grade; //학년 
	int class; //반 
	
} Student; 

int main(){
	Student stu = {"김철수", 20210000, 1, 1};
	printf("%s님의 학번은 %d 입니다. %d학년 %d반입니다.", stu.name, stu.code, stu.grade, stu.class);
}

struct student에서 student라는 이름을 빼버렸습니다.

물론 이렇게 쓰면 앞으로는 struct student라곤 쓸 수 없고 Student라고만 써야 합니다.

 

구조체 배열

 

1. 멤버 변수로 배열 사용하기

#include <stdio.h>

typedef struct student
{
	char name[100]; //구조체 멤버 변수에 배열을 사용해도 무관함. 
	int code; //학번 
	int grade; //학년 
	int class; //반 
	
} Student; //student 구조체를 Student로 쓴다. 

int main(){
	Student stu = {"김철수", 20210000, 1, 1}; 
	printf("%s님의 학번은 %d 입니다. %d학년 %d반입니다.", stu.name, stu.code, stu.grade, stu.class);
}

이건 간단하게 이해하시면 됩니다.

멤버 변수에 배열을 사용해도 문제가 없습니다.

지금까지는 이름을 표현할때 포인터 문자열을 이용했으나 이번엔 배열 문자열을 써봤습니다.

 

물론 배열로 문자열을 구현하면 99글자 제한(널문자 때문)이 걸리기 때문에 주의해야 합니다.

 

여기서 질문

그러면 stu라는 구조체 변수의 메모리 크기는 어떻게 될까요?

 

답을 우선 말해보자면 112바이트 입니다.

char이 1개당 1바이트 x 100개 = 100바이트

int형 변수가 3개 = 4바이트 x 3 = 12바이트

총 112바이트가 되게 됩니다.

 

#include <stdio.h>

typedef struct student
{
	char name[100]; //구조체 멤버 변수에 배열을 사용해도 무관함. 
	int code; //학번 
	int grade; //학년 
	int class; //반 
	
} Student; //student 구조체를 Student로 쓴다. 

int main(){
	Student stu = {"김철수", 20210000, 1, 1}; 
	printf("%d", sizeof(stu));
}

112
--------------------------------
Process exited after 0.007834 seconds with return value 3
계속하려면 아무 키나 누르십시오 . . .

sizeof 로 출력을 해보면 112바이트가 출력되는걸 알 수 있습니다.

 

2. 구조체 변수로 배열 사용하기

사실 1번은 중요하지 않았고 2번이 중요합니다.

 

지금까지는 학생을 관리할때 위와 같이 김철수 학생 한명이였지만

학생이 많아져서 구조체 변수 1000개를 만드는 상황이 오면 어떻게 될까요?

stu1, stu2,... stu1000으로 총 1000개를 만들어내야 할 것입니다.

 

예전에 배열편에서 자동차 번호 저장변수 1000개를 만들때랑 비슷한 상황이 온 것이죠.

이를 해결하기 위해 구조체 변수로 배열을 사용해봅시다.

 

#include <stdio.h>

typedef struct student
{
	char name[100]; //구조체 멤버 변수에 배열을 사용해도 무관함. 
	int code; //학번 
	int grade; //학년 
	int class; //반 
	
} Student; //student 구조체를 Student로 쓴다. 

int main(){
	Student stu[3] = {
	{"김철수", 20210000, 1, 1},
	{"김영희", 20210001, 2, 1},
	{"홍길동", 20210002, 3, 1}
	};
	
	for(int i = 0; i < 3; i++){
		printf("%s님의 학번은 %d 입니다. %d학년 %d반입니다.\n", stu[i].name, stu[i].code, stu[i].grade, stu[i].class);
	}
}

구조체 변수로 배열을 만들땐 위처럼 그냥 구조체 변수 뒤에 몇개를 만들어줄껀지

구조체변수이름[개수] 로 적어주시면 됩니다. 

 

앞의 자료형만 다를뿐 일반 변수의 배열을 만들때랑 방법이 동일합니다.

int a; -> int a[10];

 

그리고 초기화를 할때는 위처럼 중괄호로 전체를 묶고 콤마로 구분시키며 저장해주면 됩니다.

 

그리고 사용을 할때는 stu[인덱스].멤버변수 이름으로 접근을 합니다.

위 예제는 for문을 이용해서 학생 각각을 출력하는 예제입니다.

 

3. 멤버 변수로 배열 사용시 주의점

#include <stdio.h>

struct student
{
	char name[100]; //구조체 멤버 변수에 배열을 사용해도 무관함. 
	int code; //학번 
	int grade; //학년 
	int class; //반 
	
}; //student 구조체를 Student로 쓴다. 

int main(){
	struct student stu;
	stu.name = "홍길동"; //오류 발생 
	return 0;
}

해당 예제를 실행하면 stu.name 에 "홍길동" 을 할당하는 부분에서 오류가 발생합니다.

이유는 name이 배열이름이라 그렇습니다.

배열이름 = 시작주소(배열 첫번째 요소 주소) 였던거 기억나시죠?

 

시작주소에 "홍길동" 이라는 문자열을 저장하려니 안되는겁니다.

구조체 멤버 변수로 배열로 구현한 문자열을 이용할 경우 

아까처럼 선언과 동시에 초기화 하지 않으면

 

대입 연산자로는 문자열을 저장할 수 없습니다.

이것을 해결하려면 string.h에 정의되어 있는 문자열 처리 함수 strcpy() 를 사용해야 합니다.

나중에 다룰 것이므로 함수를 사용해야 한다는 것 정도만 알아두고 넘어갑시다.

 

구조체와 포인터

포인터가 끝난줄 알았지만 포인터가 또 나왔습니다..

포인터를 배워보셔서 알겠지만 포인터는 사실 메모리 주소에 직접 접근해서 값을 엑세스 할 수 있는 굉장히 유용한 도구입니다. (그와 동시에 매우 위험합니다.)

 

포인터야 앞에서 머리아픈 부분이 많았지 제대로 학습만 되어있다면 구조체에서 포인터를 사용하는 경우는 다 비슷비슷 합니다. 구조체 배열도 마찬가지 였듯이요.

 

구조체에서 포인터를 사용하는 3가지에 대해 알아봅시다.

1. 멤버 변수로 포인터 사용하기

2. 구조체 변수로 포인터 사용하기

3. 자기 참조 구조체와 외부 참조 구조체

 

 

1. 멤버 변수로 포인터 사용하기

#include <stdio.h>

struct point
{
	int * x; //구조체 멤버 변수로 포인터 변수 사용. 
	int * y;
}; 

int main(){
	int a = 4, b = 5;
	struct point pos;
	
	pos.x = &a; //포인터로 a변수 포인팅 
	pos.y = &b;
	
	printf("좌표 : (%d,%d)", *pos.x, *pos.y);
}

좌표 : (4,5)
--------------------------------
Process exited after 0.006539 seconds with return value 0
계속하려면 아무 키나 누르십시오 . . .

포인터를 제대로 이해하셨다면 쉽게 받아들이실 겁니다.

(사실 아까 student라는 구조체 정의할때 char * name 에서도 사용되었던 것이구요.)

 

point라는 구조체의 변수는 int * x , int * y 로 각각 포인터 변수입니다.

구조체 멤버변수 pos.x = &a, pos.y = &b 로

 

각각 a와 b의 주소를 포인팅하게 하였고

*pos.x 로 출력을하면 pos.x가 포인팅하고 있는 주소(&a)의 값, 즉 a가 출력되게 됩니다.

 

. 연산자가 * 연산자보다 우선순위가 높아서 pos.x가 먼저 계산되고 그 다음 *pos.x를 계산하게 됩니다.

 

그림으로 나타내면 아래와 같습니다.

 

#include <stdio.h>

struct point
{
	int x;
	int y;
}; 

int main(){
	struct point pos = {1,2};
	printf("%p %p", &pos, &pos.x);
}

000000000062FE10 000000000062FE10
--------------------------------
Process exited after 0.006439 seconds with return value 0
계속하려면 아무 키나 누르십시오 . . .

그리고 추가적으로 구조체 변수의 시작주소와 구조체의 첫번째 멤버 변수의 주소가 같습니다.

 

&pos 와 &pos.x (구조체의 첫번째 멤버 변수 주소) 는 동일한 출력 결과가 나옵니다.

 

2. 구조체 변수로 포인터 사용하기

1번의 경우에는 예전에 배웠던 포인터 변수를 단순히 구조체 멤버 변수에 쓰는 것이였죠.

그런데 구조체를 가리키는 포인터를 쓸 수도 있습니다.

 

구조체 변수로 포인터를 사용한다는 의미는 구조체 변수에 간접 접근할 수 있다는 의미입니다.

 

다음 예제는 구조체 변수로 1차원 포인터를 사용하는 코드입니다.

#include <stdio.h>

struct student
{
	char * no;
	char * name;
	int grade;
	int class;
};

int main(){
	struct student stu = {"20210000", "Hong", 1, 3};
	struct student * p = &stu; //구조체 포인터로 구조체 변수 stu 주소 가리키기. 
	
	printf("학번 : %s 이름 : %s %d학년 %d반\n", stu.no, stu.name, stu.grade, stu.class); //구조체 변수로 일반적인 접근 
	printf("학번 : %s 이름 : %s %d학년 %d반\n", (*p).no, (*p).name, (*p).grade, (*p).class); //구조체 포인터로 간접 접근 (*p 로 접근해야 함을 유의.) 
	printf("학번 : %s 이름 : %s %d학년 %d반\n", p->no, p->name, p->grade, p->class);
	
	return 0;
}

학번 : 20210000 이름 : Hong 1학년 3반
학번 : 20210000 이름 : Hong 1학년 3반
학번 : 20210000 이름 : Hong 1학년 3반

--------------------------------
Process exited after 0.007236 seconds with return value 0
계속하려면 아무 키나 누르십시오 . . .

구조체 변수로 포인터를 사용, 즉 구조체 포인터는 포인터 변수를 만들어 줄때 앞에 * 키워드만 붙여주면 됩니다.

그리고 포인터기 때문에 가리키는 구조체의 주소를 넘기면 됩니다.

 

int 형 변수를 포인팅 하려면 int 형 포인터,

char 형 변수를 포인팅 하려면 char 형 포인터를 썼듯이

 

구조체를 포인팅 하려면 구조체 포인터를 써야 합니다.

 

구조체 포인터로 가리키는 구조체에 접근하려면 두가지 방법이 있는데요

(*p).멤버변수 이름 의 형태로 접근하거나 p->멤버변수 이름

의 형태로 접근해야 합니다.

 

구조체 포인터인 p 자체는 주소(=구조체 변수 stu의 주소)를 가리키고 있기 때문에 *p 라고 해줘야만 p가 가리키고 있는 값(원본), 즉 stu라는 구조체 변수가 되게 됩니다.

 

그리고 접근할때 괄호를 써주는 이유는 아까도 말했듯이 . 연산자가 * 연산자보다 우선순위가 높아서 그렇습니다. *p에 괄호를 씌어줘서 (*p)를 먼저 계산하게 해서 원본 구조체(구조체 변수 stu)에 접근하고

그 멤버 변수에 접근해야 하는 것 입니다.

 

괄호를 쓰는게 조금 거추장 스럽다고 느끼시면 -> 연산자를 쓰셔도 됩니다. p->no 와 (*p).no, stu.no 는 전부 동일한 표현입니다.

 

포인터를 이용하여 구조체의 멤버 변수에 접근할때 .연산자보다 -> 연산자를 더 많이 사용합니다.

-> 연산자는 구조체 포인터 변수에서만 사용이 가능합니다.

 

구조체 포인터로 멤버 변수에 간접 접근할때

왜 -> 연산자를 쓰는지는, 포인터의 원래 의미를 생각해보시면 됩니다. p가 포인팅하고 있는 곳의 화살표, 즉 stu로 들어가서 no 멤버 변수에 접근한다. 이렇게 이해해주시면 되겠습니다.

 

3. 자기 참조 구조체와 외부 참조 구조체

마지막으로 구조체에서 포인터를 사용하는 세 번째 경우인 자기 참조 구조체와 외부 참조 구조체에 대해서 살펴보겠습니다. 

 

자기 참조? 외부 참조? 무슨 뜻일까요. 먼저 참조라는 말이 있으므로 포인터가 필요하다는 것은 알 수 있습니다.

 

정답부터 말하자면

자기 참조 구조체의 의미는 구조체 내에서 자기 자신을 참조하는 것이고,

외부 참조 구조체의 의미는 구조체 내에서 다른 구조체를 참조하는 것입니다.

 

<자기 참조 구조체>

struct student
{
	char * no;
	char * name;
	int grade;
	int class;
	struct student * p;
};

이 예제를 보시면 구조체 student 내에서 다시 자신의 구조체 student 를 참조할 수 있게 구조체 포인터 p를 선언한 자기 참조 구조체 입니다.

 

 

<외부 참조 구조체>

struct student
{
	char * no;
	char * name;
	int grade;
	int class;
	struct score * p;
};

이 예제는 외부 참조 구조체로 아까 자기 참조 구조체와 다르게 구조체 student 내에서 score 이라는 다른 구조체를 참조하는 포인터 p를 선언하고 있습니다.

 

물론 자기 참조 구조체, 외부 참조 구조체 모두 크게 보면 그냥 멤버 변수로 구조체 포인터 변수를 사용하는 것입니다. 이런 용어가 있다는 것만 알아두십시오.

 

활용하는 예제가 있으나 크게 중요하진 않으니 생략하도록 하겠습니다.

 

구조체의 크기

struct test1
{
	int x;
	char y;
};

중간에 짧막하게 구조체의 크기를 계산하는 것을 해봤었는데요.

그럼 다음 구조체는 크기가 어떻게 될까요?

 

추정 컨데 int는 4바이트, char은 1바이트라 구조체의 크기는 5바이트가 될 것으로 예측이 됩니다.

그러면 소스코드를 직접 실행해서 확인해보겠습니다.

 

#include <stdio.h>

struct test1
{
	int x;
	char y;
};

int main(){
	struct test1 t = {1,1};
	printf("%d", sizeof(t));
	return 0;
}

8
--------------------------------
Process exited after 0.006957 seconds with return value 0
계속하려면 아무 키나 누르십시오 . . .

예상과는 다르게 8바이트가 출력됩니다.

 

8바이트가 나오는 이유는 메모리 정렬 때문입니다.

4바이트 단위로 메모리 공간을 정렬하는 컴파일러가 있다고 할 때

컴파일러는 메모리 공간에 구조체를 4바이트 단위로 메모리 정렬을 하기 때문에

 

구조체의 크기는 구조체 멤버 변수들의 크기의 합보다 크거나 같습니다.

 

컴파일러는 구조체의 멤버 변수들 사이에 사용되지 않는 데이터 바이터를 삽입하기도 하는데 이것을 패딩(Padding) 기법이라고 합니다. 예를 들어서 4바이트 단위로 메모리 정렬을 하는 컴파일러에서 구조체의 멤버 변수들이

 

위 예제처럼

char a;

int b;

 

라고 한다면 char형은 1바이트이기 때문에 컴파일러에서 메모리 정렬을 하면서 3바이트를 삽입합니다. 

그래서 총 8바이트가 된 것 입니다.

따라서 패딩때문에 구조체의 크기가 멤버 변수들의 크기의 합보다 큰 경우가 발생합니다.

 

해당 내용에 관해 더 참고할만한 것

https://pangate.com/19

 

구조체 패딩(padding) 문제

1. 구조체와 패딩비트 아래와 같은 구조체를 선언했다고 하자. struct test_s { char a; int b; } test; char가 1바이트이고 int가 4바이트인 시스템에서 위의 구조체를 선언하고 sizeof()로 구조체의 사이즈를

pangate.com

 

2편에서 계속 됩니다..

SNS 공유하기
네이버밴드
카카오톡
페이스북
X(트위터)

최근글
인기글
이모티콘창 닫기
울음
안녕
감사
당황
피폐