C 구조체

C, C++ 2021. 9. 12. 02:48
728x90
728x90
TCP 스쿨 11.40 11.41. 11.42. 11.43 보고 필기

 

구조체(structure type)

구조체란 사용자가 C언어의 기본 타입을 가지고 새롭게 정의할 수 있는 사용자 정의 타입 (사용자정의 자료형)
배열 : 같은 타입의 변수 집합 vs 구조체 : 다양한 타입의 변수 집합을 하나의 타입으로 나타낸 것

 

구조체를 구성하는 변수

 구조체의 멤버 또는 멤버변수(member variable) 라 한다.

 

구조체 정의
struct 구조체이름
{
    멤버변수1의타입 멤버변수1의이름;
    멤버변수2의타입 멤버변수2의이름;
    ...
}; // 세미콜론에 주의

 

구조체 선언
struct 구조체이름 구조체변수이름;

 

동시에 정의와 선언하기

struct 구조체이름
{
    멤버변수1의타입 멤버변수1의이름;
    멤버변수2의타입 멤버변수2의이름;
    ...
} 구조체변수이름;

 

 

typedef  : 이미 존재하는 타입에 새로운 이름을 붙일 때 쓴다

구조체 변수를 선언하거나 사용할 때에는 매번 struct 키워드를 사용하여 구조체임을 명시해야 하는데
typedef 키워드를 사용하여 구조체에 새로운 이름을 선언하면 매번 struct 키워드를 사용하지 않아도 된다.
구조체의 정의와 typedef 선언을 동시에 할 시 구조체 이름 생략 가능하다.

 

선언하기

typedef struct 구조체이름 구조체의 새로운이름;

 

동시에 정의와 선언하기

typedef struct (구조체이름)
{
    멤버변수1의타입 멤버변수1의이름;
    멤버변수2의타입 멤버변수2의이름;
    ...
} 구조체의새로운이름;

 

 

 

구조체 멤버 접근과 초기화

구조체에서 구조체 멤버로의 접근은 멤버 연산자 (.)를 사용한다.
구조체의 주소값과 구조체의 첫 번째 멤버 변수의 주소값은 언제나 같다.

구조체변수이름.멤버변수이름

 

 

구조체 멤버 초기화

구조체 변수 초기화는 멤버 연산자(.) 와 중괄호 { } 를 사용한다.

 

원하는 멤버 변수만 초기화

원하는 멤버 변수만 초기화 가능하며, 초기화하지 않은 멤버변수는 0으로 초기화된다.

// 원하는 멤버 변수만 초기화
// 순서 상관 없음
구조체변수이름 = {.멤버변수1이름 = 초깃값, .멤버변수2이름 = 초깃값, ...};

 

배열의 초기화 방법으로 초기화

구조체 정의에서 멤버 변수가 정의된 순서에 따라 차례로 초기값 설정, 나머지 멤버변수는 0으로 초기화

// 배열의 초기화와 같은 방법으로 구조체 변수 초기화
// 정의된 순서에 따라 차례대로 초깃값이 설정되며, 나머지 멤버 변수는 0으로 초기화
구조체변수이름 = {멤버변수1의초깃값, 멤버변수2의초깃값, ...};

 

구조체 변수 초기화 예제

#include <stdio.h>  

struct book.
{
    char title[30];
    char author[30];
    int price;
};  

int main(void)
{
    struct my_book = {"HTML과 CSS", "홍길동", 28000};
    struct java_book = {.title = "Java language", .price = 30000};  

    printf("첫 번째 책의 제목은 %s이고, 저자는 %s이며, 가격은 %d원입니다.\n",
        my_book.title, my_book.author, my_book.price);
        
    printf("두 번째 책의 제목은 %s이고, 저자는 %s이며, 가격은 %d원입니다.\n",
        java_book.title, java_book.author, java_book.price);

    return 0;
}
#include <stdio.h>  

// typedef 키워드 사용으로 새 이름 선언 후 사용하는 예제
typedef struct
{
    char title[30];
    char author[30];
    int price;
}  TEXTBOOK;  

int main(void)
{
    TEXTBOOK my_book = {"HTML과 CSS", "홍길동", 28000};
    TEXTBOOK java_book = {.title = "Java language", .price = 30000};  

    printf("첫 번째 책의 제목은 %s이고, 저자는 %s이며, 가격은 %d원입니다.\n",
        my_book.title, my_book.author, my_book.price);
        
    printf("두 번째 책의 제목은 %s이고, 저자는 %s이며, 가격은 %d원입니다.\n",
        java_book.title, java_book.author, java_book.price);

    return 0;
}

 

 

 

 

구조체 배열 선언

C 언어에서 배열의 요소가 될 수 있는 타입에는 제한이 없다. 즉 구조체도 배열의 한 요소가 될 수 있다.

구조체 배열 선언은 다른 타입의 배열을 선언하는 방법과 같고,
구조체 배열에서 각 배열 요소로 접근하는 방법도 일반 배열의 접근 방법과 같고,
멤버 연산자(.)를 사용하여 각 배열 요소의 멤버에 접근한다.

#include <stdio.h>

struct book
{
	char title[30];
	char author[30];
	int price;
};

int main(void)
{
	struct book text_book[3] = 
	{
		{"국어", "홍길동", 15000},
		{"영어", "이순신", 18000},
		{"수학1", "강감찬", 10000}
	};
	
	puts("각 교과서의 이름은 다음과 같습니다.");
	printf("%s, %s, %s\n", text_book[0].title, text_book[1].title, text_book[2].title);
	return 0;
}

구조체 배열 text_book의 메모리 상태

 

 

구조체를 가리키는 포인터

구조체 변수를 가리키는 포인터는 다음처럼 선언한다.

// struct 구조체이름* 구조체포인터이름;
struct book* ptr_my_book;

 

구조체의 주소값과 구조체의 첫 번째 멤버 변수의 주소값은 언제나 같다. 그러나
배열의 경우와 달리 구조체의 이름은 구조체를 가리키는 주소가 아니다.
즉 포인터에 할당할 때에는 구조체는 반드시 주소 연산자(&)를 사용해야 한다.

 

 

구조체 포인터를 이용하여 구조체의 멤버에 접근하는 방법

구조체 포인터를 이용하여 구조체의 멤버에 접근하는 방법은 아래 두 가지이다.
화살표 연산자가 좀 더 많이 사용된다.

1. 참조 연산자(*) 이용
참조 연산자(*)는 멤버 연산자(.)보다 연산자 우선순위가 낮으므로 반드시 괄호(())를 사용

// (*구조체포인터).멤버변수이름
(*ptr_my_book).author


2. 화살표 연산자(->) 이용

// 구조체포인터 -> 멤버변수이름
ptr_my_book -> author

 

예시

#include <stdio.h>
#include <string.h>

struct book
{
	char title[30];
	char author[30];
	int price;
};

int main(void)
{
	struct book my_book = {"C언어 완전 해부", "홍길동", 35000}; //title, author, price
	struct book* ptr_my_book;	// 구조체 포인트 선언 
	
	ptr_my_book = &my_book; 
	
	strcpy((*ptr_my_book).title, "C언어 마스터");	// 참조 연산자(*)를 이용하는 방법
	strcpy(ptr_my_book->author, "이순신");			// 화살표 연산자(->)를 이용하는 방법
	my_book.price = 32000;							// 구조체 변수를 이용한 직접 수정 
	
	printf("책의 제목은 %s이고, 저자는 %s이며, 가격은 %d원입니다.\n", my_book.title, my_book.author, my_book.price);
	return 0;
}

 

 

함수와 구조체

C에서는 함수를 호출할 때 전달되는 인수나, 함수가 종료될 때 반환되는 값으로 구조체를 사용할 수 있고
기본 타입과 똑같은 방식으로 사용한다.
구조체를 가리키는 포인터나 구조체의 한 멤버변수만을 사용할 수도 있다.

 

구조체를 인수로 전달하는 예제

구조체를 인수로 전달하면 장점 : 함수가 원본 구조체의 복사본을 갖고 작업하여 안전하다.

#include <stdio.h>
#include <typeinfo>    
#include <iostream>    

typedef struct
{
	int savings;
	int loan;	
} PROP;

int calcProperty(int, int);

int main(void)
{
	int hong_prop;
	PROP hong =	{10000000, 4000000};
	
	hong_prop = calcProperty(hong.savings, hong.loan);	// 구조체의 멤버 변수를 함수의 인수로 전달함 
	
	printf("홍길동의 재산은 적금에 %d원 대출 %d원을 제외한 총 %d원입니다.\n", hong.savings, hong.loan, hong_prop);
	
	std::cout << typeid(hong).name() << std::endl;
	
 	printf("%s\n", typeid(hong_prop).name());
 	printf("%s\n", typeid(calcProperty).name());
	
	return 0;
}

int calcProperty(int s, int l)
{
	return (s - l);
}

 

 

함수의 인수로 구조체의 주소를 직접 전달하는 예제

구조체 포인터를 인수로 전달하는 방식의 장점 : 구조체의 복사본이 아닌 주소 하나만 전달해로 처리가 빠르다. 
하지만 호출된 함수에서 원본 구조체에 직접 접근하므로 원본 데이터의 보호 측면에서는 매우 위험

 

#include <stdio.h>

typedef struct
{
	int savings;
	int loan;	
} PROP;

int calcProperty(PROP*);

int main(void)
{
	int hong_prop;
	PROP hong =	{10000000, 4000000};
	
	hong_prop = calcProperty(&hong);	// 구조체의 주소를 함수의 인수로 전달함. 
	
	printf("홍길동의 재산은 적금 %d원에 대출 %d원을 제외한 총 %d원입니다.\n", hong.savings, hong.loan, hong_prop);
	return 0;
}

int calcProperty(PROP* money)
{
	money->savings = 100;	// 호출된 함수에서 원본 구조체의 데이터를 변경
	return (money->savings - money->loan);
}

 

 

보완 방법 : const를 사용하여 전달된 인수를 함수 내에서 직접 수정 못하게 한다.

#include <stdio.h>

typedef struct
{
	int savings;
	int loan;	
} PROP;

PROP initProperty(void);
int calcProperty(const PROP*);

int main(void)
{
	PROP prop;
	int hong_prop;
	
	prop = initProperty();
	hong_prop = calcProperty(&prop);
	
	printf("홍길동의 재산은 적금 %d원에 대출 %d원을 제외한 총 %d원입니다.\n", prop.savings, prop.loan, hong_prop);
	return 0;
}

PROP initProperty(void)
{
	PROP hong =	{10000000, 4000000};
	return hong;	// 구조체를 함수의 반환값으로 반환함.
}

// const 키워드를 사용하여 구조체의 데이터를 직접 수정하는 것을 방지
int calcProperty(const PROP* money)	
{
	return (money->savings - money->loan);
}

 

initProperty() 함수는 반환값으로 구조체를 직접 반환한다.
기본적으로 C언어의 함수는 한 번에 하나의 데이터만을 반환할 수 있으나
구조체를 사용하면 여러 개의 데이터를 한 번에 반환할 수 있다.

 

 

중첩 구조체

C에서는 구조체를 정의할 때 멤버 변수로 다른 구조체를 포함할 수 있다.

#include <stdio.h>

struct name
{
	char first[30];
	char last[30];
};

struct friends
{
	struct name friend_name;
	char address[30];
	char job[30];
};

int main(void)
{
	struct friends hong = 
	{
		{ "길동", "홍" },
		"서울시 강남구 역삼동",
		"학생"
	};
	
	printf("%s\n\n", hong.address);
	printf("%s%s에게,\n", hong.friend_name.last, hong.friend_name.first);
	printf("그동안 잘 지냈니? 아직 %s이지?\n", hong.job); 
	puts("공부 잘 하고, 다음에 꼭 한번 보자.\n잘 지내^^");
	return 0;
}

 

 

구조체의 크기

구조체의 크기는 멤버 변수들의 크기에 따라 결정되나 언제나 멤버 변수들의 크기 총합과 같지는 않다. 
구조체 크기 변수 크기 총합과 다른 이유 : 흰트 (패딩)

더보기

c는 구조체의 멤버 중 가장 크기가 큰 걸 기준으로 그 배수로 구조체의 사이즈를 늘려나간다.
그리고 기준보다 작은 멤버들은 패딩을 더해 기준과 같게 만든다.
패딩이 생기는 이유는 CPU가 메모리에 접근하는 걸 최소화해 속도를 빠르게 하기 위해서다. 
그래서 멤버의 위치만 바껴도 구조체의 크기가 달라지기도 한다. 
구조체의 최적화를 위해서 멤버의 위치도 고려해야 한다.

 

예시

#include <stdio.h>

typedef struct
{
	char a;
	int b;
	double c;
} TYPESIZE;

int main(void)
{
	puts("구조체 TYPESIZE의 각 멤버의 크기는 다음과 같습니다.");
	printf("%d %d %d\n", sizeof(char), sizeof(int), sizeof(double));
	
	puts("구조체 TYPESIZE의 크기는 다음과 같습니다.");
	printf("%d\n", sizeof(TYPESIZE));
	return 0;
}

 패딩 바이트(padding byte)는 3바이트

 

 

바이트 패딩 (byte padding)

구조체를 메모리에 할당할 때 컴파일러는 프로그램 속도 향상을 위해 바이트 패딩 규칙을 사용한다.
구조체는 다양한 크기의 타입을 멤버변수로 가지는 타입이며, 컴파일러는 메모리 접근을 쉽게 하려고 크기가 가장 큰 멤버 변수를 기준으로 모든 멤버변수의 메모리 크기를 맞춘다. 이게 바이트 패딩이며 아까도 더보기에 설명한 부분이다.
이 때 추가되는 바이트를 padding byte라 한다.

 

 

공용체 (union)

거의 구조체와 같은데, 모든 멤버 변수가 하나의 메모리 공간을 공유한다는 점이 구조체와 차이점이다.
모든 멤버변수가 같은 메모리를 공유해 공용체는 한 번에 하나의 멤버 변수밖에 사용할 수 없다.

 

순서가 불규칙하며 미리 알 수 없는 다양한 타입의 데이터 저장용으로 나온 타입이다.
가장 큰 멤버 변수의 자료형의 공간(크기)를 공유한다.

 

union A {
	int x; //가장 큰 int 자료형의 공간 공유
	char y;
}
// 공용체 정의
union 공용체이름 {
	자료형 멤버이름;
};

// 공용체 변수 선언
union 공용체이름 변수이름;

// 동시에 정의, 선언
union 공용체이름{
	자료형 멤버이름;
} 변수;

// 동시에 정의, 별칭
typedef union 공용체이름{
	자료형 멤버이름;
} 공용체별칭;

// 익명 공동체
typedef union {
	자료형 멤버이름;
} 공용체별칭;


공용체 멤버 접근은 (.) 사용
공용체 배열을 사용하면 같은 크기로 구성된 배열 요소에 대한 다양한 크기의 데이터를 저장할 수 있다.

 

공용체 멤버 변수를 하나만 초기화했는데 나머지 멤버 변수들이 같은 데이터를 공유하는 걸 알 수 있는 예제

#include <stdio.h>

typedef union
{
	unsigned char a;
	unsigned short b;
	unsigned int c;
} SHAREDATA;

int main(void)
{
	SHAREDATA var;
	var.c = 0x12345678;
	
	printf("%x\n", var.a);
	printf("%x\n", var.b);
	printf("%x\n", var.c);
	return 0;
}

 

 

 

열거체 (enumerated types)

새로운 타입을 선언하면서, 동시에 해당 타입이 가질 수 있는 정수형 상수값도 같이 명시할 수 있는 타입.
열거체 사용의 장점 : 프로그램의 가독성 좋아짐, 변수가 지니는 값에 의미 부여 가능

enum 키워드를 사용해서 선언한다.
사용자가 별도로 멤버에 해당하는 상수값을 명시할 수 있다.
명시하지 않으면 0부터 시작되며, 그 다음 멤버 값은 바로 앞 멤버값보다 1만큼 증가하여 정의된다.

enum Weather {SUNNY = 0, CLOUD = 10, RAIN = 20, SNOW = 30};

 

예제

#include <stdio.h>

enum Weather {SUNNY = 0, CLOUD = 10, RAIN = 20, SNOW = 30};

int main(void)
{
	enum Weather wt;
	
	wt = SUNNY;
		
	switch (wt)
	{
		case SUNNY:
			puts("오늘은 햇볕이 쨍쨍!");
			break;
		case CLOUD:
			puts("비가 올락말락하네요!");
			break;
		case RAIN:
			puts("비가 내려요.. 우산 챙기세요!");
			break;
		case SNOW:
			puts("오늘은 눈싸움하는 날!");
			break;
		default:
			puts("도대체 무슨 날씨인건가요!!!");
	}
	
	puts("각각의 열거체 멤버에 해당하는 정수값은 다음과 같습니다.");
	printf("%d %d %d %d\n", SUNNY, CLOUD, RAIN, SNOW);
	return 0;
}

 

#include <stdio.h>

enum Days {MON, TUE, WED, THU, FRI, SAT, SUN};

int main(void)
{
	enum Days today;
	
	today = SAT;
	
	if (today >= SAT && today <= SUN)
	{
		puts("오늘은 주말이네요~ 주말에도 열심히 공부하는 여러분은 최고에요!");
	}
	else
	{
		printf("주말까지 %d일 남았어요~ 조금만 더 힘내자구요!", 5 - today);
	}

	puts("각각의 열거체 멤버에 해당하는 정수값은 다음과 같습니다.");
	printf("%d %d %d %d %d %d %d\n", MON, TUE, WED, THU, FRI, SAT, SUN);
	return 0;
}

 

 

 

구조체와 비트 

지금까지 구조체의 멤버는 각 자료형 크기만큼 공간을 차지했는데
구조체 비트 필드를 사용하면 구조체 멤버를 비트 단위로 저장할 수 있다.

CPU나 기타 칩의 플래그를 다루는 저수준(low level) 프로그래밍을 할 때 기본 자료형보다 더 작은 비트 단위로 값을 가져오거나 저장하는 경우가 많으므로 구조체 비트 필드가 유용하게 사용

모든 정수 자료형을 사용할 수 있고 보통 부호 없는(unsigned) 자료형을 주로 사용한다.
실수 자료형은 비트 필드로 사용할 수 없다. 멤버를 선언할 때 : (콜론) 뒤에 비트 수를 지정해준다.

struct 구조체이름 {
    정수자료형 멤버이름 : 비트수;
};

 

사용 예제

#include <stdio.h>

struct Flags {
    unsigned int a : 1;     // a는 1비트 크기
    unsigned int b : 3;     // b는 3비트 크기
    unsigned int c : 7;     // c는 7비트 크기
};

int main()
{
    struct Flags f1;    // 구조체 변수 선언

    f1.a = 1;      //   1: 0000 0001, 비트 1개
    f1.b = 15;     //  15: 0000 1111, 비트 4개
	// 비트 필드에 지정한 비트수보다 할당한 비트 수가 많다
    f1.c = 255;    // 255: 1111 1111, 비트 8개

    printf("%u\n", f1.a);    //   1:        1, 비트 1개만 저장됨
    printf("%u\n", f1.b);    //   7:      111, 비트 3개만 저장됨
    printf("%u\n", f1.c);    // 127: 111 1111, 비트 7개만 저장됨
    
    printf("%d", sizeof(struct Flags));    // 4: 멤버를 unsigned int로 선언했으므로 4

    return 0;
}

모자라서 할당한 값과 다른 값이 나온다.

 

 

비트 필드 구조체의 크기는 예제에서 멤버를 unsigned int로 선언하여 4가 나온다.
비트필드의 멤버를 선언하는 자료형보다 큰 비트 수는 지정할 수 없다. 

unsigned int a : 37; // error

32비트까지 가능.

 


비트 필드에는 지정 비트 수만큼만 저장되고 나머지 비트는 버려진다. 
비트 필드의 각 멤버는 최하위비트 (Least Significant Bit, LSB)부터 차례로 배치된다.
예제에서는 a가 최하위비트에 온다.

 

https://dojang.io/mod/page/view.php?id=472

 

 

 

비트 필드와 공용체 함께 사용

코드에서 값을 지정 시 비트 필드를 사용하지만 CPU나 칩에 값 설정 시 모든 비트를 묶어서 한번에 저장한다.
한번에 저장하는 방법은 공용체를 비트 필드와 함께 사용하는 것.
비트 필드로 사용할 멤버는 익명 구조체로 감싼다.

 

 

#include <stdio.h>

struct Flags {
    union {    // 익명 공용체
        struct {    // 익명 구조체
        // 비트 수 지정
            unsigned short a : 3;    // a는 3비트 크기
            unsigned short b : 2;    // b는 2비트 크기
            unsigned short c : 7;    // c는 7비트 크기
            unsigned short d : 4;    // d는 4비트 크기
        };                           // 합계 16비트
        unsigned short e;    // 2바이트(16비트)
    };
};

int main()
{
    struct Flags f1 = { 0, };    // 모든 멤버를 0으로 초기화

	// 비트 필드에 값 할당 
    f1.a = 4;     //  4: 0000 0100  
    f1.b = 2;     //  2: 0000 0010 
    f1.c = 80;    // 80: 0101 0000  
    f1.d = 15;    // 15: 0000 1111  
 
    printf("%u\n", f1.e);    // 64020: 1111 1010000 10 100

    return 0;
}

 

 

 

 

 

 

문제 : Flags 안을 채워서 3, 1, 63이 출력되게 하시오.
#include <stdio.h>

struct Flags {
    
};

int main()
{
    struct Flags f1;

    f1.a = 0xffffffff; 
    f1.b = 0xffffffff; 
    f1.c = 0xffffffff; 

    printf("%u %u %u\n", f1.a, f1.b, f1.c);

    return 0;
}

 

해설 : 비트 수를 조절하여 값을 도출한다.
0xffffffff 는 1이 32개므로 모든 비트가 1로 채워진다. 각 값이 1이 몇 개인지 판단하면 된다.
3, 1, 63을 표현하려면 3은 1이 2개, 1은 1이 1개, 63은 1이 6개 필요하다.

#include <stdio.h>

struct Flags {
    unsigned int a : 2;
    unsigned int b : 1;
    unsigned int c : 6;
};

int main()
{
    struct Flags f1;

    f1.a = 0xffffffff; 
    f1.b = 0xffffffff; 
    f1.c = 0xffffffff; 

    printf("%u %u %u\n", f1.a, f1.b, f1.c);

    return 0;
}

 

 


 

 

참고한 곳

TCP 스쿨 : http://tcpschool.com/c/c_struct_intro

코딩공장 : https://coding-factory.tistory.com/639

모두의 코드 : https://modoocode.com/55

 

 

728x90
728x90
블로그 이미지

coding-restaurant

코딩 맛집에 방문해주셔서 감사합니다.

,

v