본문 바로가기

무언가 만들기 위한 지식/C,C++,Embedded C

[C] struct(구조체)의 선언과 사용


내일 당장 C를 이용한 동적할당과 struct를 사용해야 해서 미리 연습을 좀 하였다.
일단 struct이란 C에서 서로 다른 자료형들을 모아 관리하기 위해 사용된다.
배열의 경우 무조건 같은 자료형만을 취급할 수 있기 때문에 구조체는 상당히 유용하게 사용된다.
CPP를 이용할땐 class를 사용하지만 C를 사용할때는 struct을 사용해야 한다.
(물론 cpp에서도 struct을 사용하긴한다.)

필자의 기억으로는 struct을 선언하는 부분에서 이외로 까다로운 부분이 많았었다.
그래서 이런저런 테스트를 해보았다. (간단한 선언 및 사용이지만..)

결론은 뭐 역시 구조체도 typedef로 사용하는편이 간편하다^^;
그리고 구조체도 포인터를 이용하여 포인터연산자(->)을 사용하는게 편하다.
(이 경우 malloc, calloc을 이용한 동적할당을 해주면 좋음)

필자가 내일 사용할 구조체의 내용은 화면상의 그림의 위치 및  bmp 배열 3가지를 모아서 관리할 struct을 만드는 것이다. 차후 배열의 자료형을 struct포인터를 이용하여 관리할 예정이다. 아무튼 struct의 자료형으로는 x, y좌표와 배열이다. 배열의 경우 다른 문서에 선언하고 int *arry를 통해 위치 정보를 저장할 것이다. 

기본적인 stuct의 사용법은
[struct 태그명 {항목.....} 식별자]
이다.

위 선언에서는 태그명을 사용하지 않은 예이다.
태그명은 사용하지 않아도 된다. 단 한번 식별자를 선언하고 나서, 다시 재활용하기 위해서는 struct Code부분을 다시 작성하여야 한다. 그렇기 때문에 가능하면 태그명을 사용하는 것이 바람직하다.
위에서 PICINF가 식별자로 PICINF에 구조체(struct)가 들어가는 것이다.

위 선언에서는 태그명을 사용한 예이다. 이렇게 태그명을 사용하면, 선언부에서
struct pic a,b,c;
처럼 계속 사용이 가능하다. 가장 기본적인 struct 사용법이라고 할 수 있다. 보통 main 밖에서 선언후 사용한다.
물론 식별자는 사용하지 않아도 상관없다.

위 선언은 typedef를 사용한 가장 이상적인 사용법이다.
필자가 struct 사용법이 헷갈렸던 이유가 바로 typedef를 이용한 선언이었는데,
전에 공부한 typedef 덕분에 지금 보면 너무나 당연하다.^^;
식별자 부분을 자료형으로 사용하기 때문에 이것저것 헷갈렸다.
하지만 헷갈릴 필요 없이, typedef를 선언할때 다음에 나오는 변수명을 선언된 자료형으로 생각한다고 보면된다.
즉 위에서 선언된 변수명 PICINF를 앞으로 struct pic으로 사용한다는 의미가 된다.
태그명은 삭제해도 아무런 제약이 없으나, 작성시 struct들의 구분을 위해 가능하면 쓰는게 가독성이 있다고 본다.
선언부에서는
PICINF temp;
로 사용된다.

혹시 포인터를 사용하려면 다음과 같이 사용하기도 한다.

위에서 처럼 포인터화된 것을 다시 typedef를 이용하여 사용하기도 하지만,
가장 단순하게 하려면 그냥
PICINF *temp;
식으로 사용시 *를 직접 이용하면 된다.

struct을 사용시 가장 유의해야할 점은 인자 전달 및 return시 이다.!
왜 그러냐 하면, struct를 리턴형이나 인자로 전달할때는 배열처럼 주소가 전달되어 reference로 사용되는 것이 아니라,
값이 복사되어 전달되기 때문이다.
이는 시스템을 느려지게 하거나(매우 미세할 지라도) 의도와는 다른 수행을 할지도 모른다.
그렇기 때문에 가능하면 인자전달을 call by reference 방식으로 주소를 전해주는 편이 좋다.
왜 값의 복사가 일어나는지에 대해 고려해보면
가장 간단한 이유는 struct라는 자료형이 기본형이기 때문이다.
배열이나 함수는 기본형이 아니기 때문에 return값이 될수 없다. 그러나 struct 그 키워드 자체가 기본형이기 때문에 기본적으로 call by value로 복사가 발생한다.
이러한 점을 염두에 두고 코딩을 해야 한다.

마지막으로 struct을 이용하여 간단한 예제를 작성하였다.
[작성환경 : Window XP  작성툴 : Visual Studio 6.0  컴파일러 : Visual Studio 6.0  사용언어 : C]

3~6 Line : typedef를 이용하여 구조체를 선언하였다.
8~14 Line : 포인터 배열을 이용하여 각 인덱스마다 malloc을 이용하여 동적할당을 해준다. 그후 초기화
15~17 Line : 배열에 있는 포인터로 접근하여 가리키고 있는 구조체의 값을 출력한다. 포인터이기 때문에 "->"연산자를 이용하여 접근한다. 포인터가 아닌 단순 구조체일 경우 내부 자료에는 "."을 이용하여 접근한다. array크기 계산시 sizeof를 이용하여 전체 배열 크기를 배열 요소의 자료형으로 나누어 크기를 구하는 방법을 이용해 보았다^^;


관련글을 보다가 struct(구조체)를 0으로 초기화 에 대한 코드를 좀 보다가 예제를 작성해보았다.
[작성환경 : Window XP  작성툴 : Visual Studio 6.0  컴파일러 : Visual Studio 6.0  사용언어 : C]

0으로 초기화 방법으로는 두가지 방법이 있다.
12 Line에서처럼 memset함수를 이용하는 방법과
16 Line에서처럼 temp={0}으로 초기화하는 방법이다.
16 Line의 {0}을 이용하는 방법은 처음 값을 0으로 하고 나머지는 default로 자동으로 0이 되게하는 방법이다.
이는 초기화, 즉 처음 선언시에만 사용이 가능한 방법이다.(즉 다른 코드 중간에서는 사용못한다. 말그대로 초기화만)
12 Line의 경우 memset을 사용하면 어느 시점에나 사용이 가능하나, 단점은 역시 0으로 밖에 설정할 수 밖에 없다.
그 이유는 memset 의 원형에 기인하는데, memset의 타입은
void * memset (void *, int, size)이다.
첫번째 인자는 채울 대상이 되는 메모리
두번째 인자는 int 형이지만 byte로 인식되는 채울 값
세번째 인자는 byte로 채울 개수 이다.
메모리 공간 개념으로 memset을 사용해야 한다.
어떻게 해석해보면 byte 단위로 size크기만큼 채운다는 것이다.
즉 int ary[10]에 1모든 값을 초기화는 불가능하다. 1byte로 채우기 때문에 4byte를 1로 나눠서 4번 하니 1이 아니라 다른 이상한 수가 각 요소값에 저장이 될 것이다.
memset을 사용할려면 각 크기의 값을 계산해서 사용되어야 할 것이다.

생각해보니 예전에 memset을 main 내부에서 선언된 배열의 값들을 모두 0으로 셋팅하기 위해 사용했었던 기억이 난다. 아무튼 3번째 인자만 잘 조절해준다면 struct에서도 사용이 가능하다.
(잘 조절한다고 해봤자 sizeof 한방이면 해결_byte단위로 값을 넣으니까~~~ _sizeof 단위는 byte)


<결과 값, 초기화 된 모습과 안된 모습 확인>