C언어

C언어의 기초 - 구조체와 공용체

머리큰개발자 2021. 2. 3. 23:31

*주의 - 혼자 학습한 내용이므로 틀린 내용이 있을 수 있으니 알려주시면 진심으로 감사드리겠습니다.

 

1. 구조체(Structure)

 

같은 타입의 데이터가 여러 개 모여있는 것을 배열이라고 부른다. 만약 다른 타입의 데이터를 같은 곳에 묶어 여러개를 선언하고 싶다면 어떻게 해야할까?

 

이때 쓰이는 개념이 구조체이다. 

내가 원하는 타입대로 만드는 덩어리이므로 전역변수로 선언을 먼저 해야 어디서든 쓸 수 있다.

기본적인 문법은 다음과 같다.

#include <stdio.h>

struct Tag1{
	int a;
    char b[20];
    double c;
};

struct Tag2{
	int a;
    char c[20];
    double b;
}x={1, "HELLO~", 2.14};

int main(){
	struct Tag1 y= {1,"HI", 3.14};
    // struct Tag1 y;
    // y.a=1; y.b[0] = 'H'; y.b[1] = 'I', y.c=3.14;
    printf("%d %s %f\n", y.a, y.b, y.c); //1, HI, 3.140000
    printf("%d %s %f", x.a, x.b, x.c); //1, HELLO~, 2.140000
}

struct 라는 타입을 기재해주고, 그 struct의 tag를 지정하여 struct 중에 어떤 struct 인지 구별해준다. 그 후 구조체의 이름을 지정해주고 어떤 데이터들을 가지는지 안에 기입해주면 된다. 그리고 블럭이 끝나는 곳에 세미콜론; 을 잊지 말자.

구조체는 선언과 동시에 초기화가 가능한데, Tag2 의 경우처럼 선언 바로 뒤에 이름을 지정하고 초기화를 진행할 수 있다. 다른 하나는 Tag1 처럼 블럭 안에서 선언하고 초기화를 시키는 방법이 있다.

만약 초기화를 시키지 않는다면 주석처럼 멤버별로 . 연산자를 통해 접근하여 직접 값을 대입해야한다.

 

구조체의 크기는 내부의 멤버들에 의존한다. 가령 Tag1 의 경우, int a (4) + char b[20] (20) + double c (8) = 32B 가 나올 것이다. 하지만 char 형의 경우 1B만 차지하고, 4B 이상의 변수들은 주소가 4단위로 변하기 때문에(16진수, 32비트 운영체제 기준) char 배열이 아닌 일반 타입이었다면 int a(4) + char b(1) + double c(8) = 16B 가 나와버리게된다. 왜냐하면 char형이 1B만 차지하더라도 메모리 주소값은 short char를 제외하면 4단위로 떨어지기 때문에 3B를 쓰지 않고 건너뛰어 저장하기 때문이다. 게다가 컴파일러마다 다르지만, 나름의 최적화를 거칠 경우, 구조체가 복잡해질수록 어떻게 될지 쉽게 예측하기 어렵기 때문에 머리로 계산하지 말고 sizeof 를 사용하는 것이 좋다.

 

구조체의 이름은 배열과 다르게 주소값이 아니다. 구조체는 변수의 이름과 같이 작동을 한다. 그렇기 때문에 이름으로는 Tag1[1] 의 형식으로 접근하는 것이 불가능하며, 멤버별로 접근하는 것은 . 연산자나 나중에 볼 내용인 포인터로 주소를 받아 -> 연산자를 통해 접근할 수 있다.

변수의 이름처럼 작동하기 때문에 유효범위 또한 변수의 이름처럼 작동한다. 예로 {int Tag1=5;} 가 블럭안에 선언될 경우 Tag1 은 structure가 아닌 정수로 작동하며 블럭이 끝나면 다시 structure 를 지칭하는 말로 바뀐다.

 

배열과 구조체의 (체감상) 가장 큰 차이점은 역시 직접 대입이다.

위의 선언에서 x=y 로 직접 대입해도 배열과 달리 정상적으로 복사가 된다. 이는 배열의 전달 방법인 call by address 가 아닌 call by value 로 값들만 복사해 올 수 있기 때문인데, 이 역시 포인터에서 자세히 다룬다.

 

2. 열거형(enumeration)

 

열거형은 이름에 상수값을 집어넣기 위해 주로 사용한다. 

기본적인 문법은 다음과 같다.

enum student{kim, lee, park=-1, song, choi};

첫 인자부터 0씩 값을 부여하며 printf("%d", kim); 은 kim이 0인 것으로 작동한다.

그 다음 인자부터 1씩 증가하는 값을 가지게 되는데, 중간에 이름별로 초기화하는 것도 가능하며, 초기화했을 때, 그 다음 수부터 1씩 증가하며 값을 부여한다. 즉 여기선 kim=0, lee=1, park=-1, song=0, choi=1 로 작동하게 된다.

 

3. 공용체(Union)

 

공용체의 구조나 방식은 모두 구조체와 동일하다. 대입도 되는 것 같은데 오류가 뜬다(VS2019 기준)

다만 구조체와 다른점은, 모든 데이터들이 같은 메모리 공간을 사용한다는 것이다.

예로 

union tag1{

    int a;

    char b[8];

};

의 경우 멤버 중 가장 크기가 큰 char b[8] 의 20B가 메모리로 잡히고 먼저 선언된 순서대로 앞에서부터 할당된다. 

char b[8] 이 메모리 상에서 0x1000 ~ 0x1007 까지 잡힌다면 int a는 0x1000 부터 0x1003 까지 잡히게 된다. 그렇기 때문에 int a 의 값을 바꿀 경우에 b[0]~ b[3] 까지의 숫자도 같이 바뀌게 된다.

또한 생각해볼 것은 intel 기반일 경우에 little endian 이 쓰이기 때문에 빠른 주소에 작은 자릿수가 저장되게 된다는 것이다. 

예를 들어 union tag1 x ={0x123456789abcdef0}; 일 경우에 메모리가 0x1000에 f0, 0x1001에 de, 0x1002에 bc, 0x1003 에 9a, 0x1004에 78, 0x1005에 56, 0x1006에 34, 0x1007에 12 가 저장된다. 이럴 경우에 a 는 작은 주소부터 차지하므로 a= 0x9abcdef0; 이 된다.