본문 바로가기

잡동사니

구조체와 const에 관하여

25 typedef struct Hello Hello;
26
27 /*## class Hello */
26
29  struct Hello { 
30	RIC_EMPTY_STRUCT
31	};
32
33
34	/* User explicit entries */
	
    /* Operations */
    
38
39  /* # operation print () */ 
40  void Hello print (Hello* const me);
41
42	/* User implicit entries */
43
***
44
45 /* Constructors and destructors:*/
46 
47 
48 /*## auto generated */ 
49 void Hello Init (Hello* const me); 
50 
51 /*## auto generated */ 
52 void Hello Cleanup (Hello* const me);
53
54 /** auto generated */ 
55 Hello Hello_Create(); 
56 /** auto generated */ 
57 void Hello Destroy (Hello* const me); 

				그림 1.15) Generated header file

 

C를 사용한지 얼마 안 되는 프로그래머는 C 프로그래밍을 할 때 int, short, long, double, char, char*, float등과 같은 primitive type - 언어에서 제공해주는 - 과 함수만으로 프로그램 을 짜는 경우가 있다. 그러나, 노련한 C 프로그래머라면 프로그램을 짜기 위해서 필요한 struct를 먼저 만들면서 시작할 것이다.

OOP에 익숙한 프로그래머들은 class를 먼저 찾아서 만드는 것으로 시작하는데, C로 프로그램을 짜도 사실 여기서 크게 다르지 않다. 먼저 struct를 만들어서 시작을 해야 하는 것이다. 다음으로는, 이 struct들을 이용하는 함수를 만들어야 하는데 이를 위해서는 이 struct인자를 함수로 보내주어야 할 필요성이 생긴다. (왜냐하면, 이 구조체의 객체를 이용하여야 하므로)

즉, Hello 구조체를 이용하려는 함수는 void func(Hello hello)와 같은 식으로 인자로 Hello를 받아서 그 Hello 객체를 가지고 일을 하게 되는 것이다. 그런데, 만약 void func(Hello hello) 같은 식으로 hello를 value값으로 넘기면 우선 비효율적이라는 문제가 생긴다. 예를 들어서Hello 구조체가 100Kb의 값을 가진다고 하면, 100Kb의 메모리가 복사가 되어야 한다. 또한 value로 보내면 모든 것이 그냥 복사가 되는 것이므로 hello의 값을 직접 바꾸지를 못하게 된다. 그런 이유로 당연히 함수는 void func(Hello* hello)로 포인터 형으로 넘기도록 선언이 되야 한다.

Note) const keyword 에 대하여

Rhapsody에서 생성된 코드를 보면 void func(Hello* const hello) 과 같이 const 키워드가 포 함되어있는 것이 보일 것이다. 사실, C언어에서는 const 키워드가 있으나 그 중요성이 크게 부각되어 있지는 않다. const 키워드의 중요성은 C++에서 굉장히 강조가 되고 있으나 (const correctness로 불린다.) 오히려 Java에 와서는 전혀 강조가 되고 있지 않고 일부 기능만 final 키워드를 통하여 구현하고 있다.

하지만, const의 올바른 사용은 코딩 에러를 막는데 도움이 되므로 여기서 잠깐 언급해보도 록 하자. 위의 예에서 나온 Hello* const hello의 의미는 hello라는 포인터가 다른 객체를 가리키도록 바뀔 수가 없다는 의미이다. 즉, 다음과 같은 코드는 에러이다.

 

void func(Hello* const hello){

hello = NULL; // error

hello = (Hello*)malloc(sizeof(Hello)); // error

Hello h; hello = &h; // error

}

 

이렇게 const를 붙이는 것이 의미가 있는 것이 위의 함수가 Hello 객체를 위한 함수라면 그 객체를 일을 시키는 것이 목적이므로 저렇게 함수 안에서 자신이 가리키는 객체가 바뀔 이유가 없고, 만약 바뀐다면 그것이 에러인 상황이기가 쉽기 때문이다. 가리키는지 곳이 상수인 위와 같은 상수 포인터는 널리 쓰이지는 않는다. 그렇지만, Rhapsody는 이 const를 자동으로 생성하는 함수에서 사용하고 있으므로 그 용법을 기억하도록 하자.

이번에는 좀더 흔하게 쓰이고 더 중요한 const의 용법에 대해서 살펴보자. 위에서 포인터 변수를 쓰는 이유가 value를 쓰는 것보다 좋은 이유가 효율적이면서 원래 객체의 값을 변경할 수 있기 때문이라고 얘기를 했다. 하지만, 원래 객체의 값을 변경시킬 수 있다는 점은 어떤 경우에는 그 함수를 사용하는데 주저하게 할 수 있다. 물론 이건 그 함수 가 어떻게 작성되어 있는지 모르는 라이브러리 함수를 사용할 때와 같은 경우이다. 예를 들어 어떤 라이브러리에 있는 함수를 사용하려고 하는데 이 함수의 원형이 다음과 같다고 하자.

typedef struct FooTag

{

int a;

} Foo;

void bar(Foo* foo);

bar 함수는 Foo의 객체의 포인터를 받아서 무엇인가 하는 함수이다. 물론, 라이브러리를 쓰 다면 보통은 저 bar 함수에서 무슨 일을 하는지는 매뉴얼이나 헬프 파일에 나오게 되지만 일반적으로 함수 내부 implementation에 대해서 나오지는 않게 된다. 이 함수를 사용하기 위해서

Foo foo;

foo.a = 0; bar(&foo);

이런 식으로 쓸 때, foo가 가지고 있는 a의 값은 0이다. 그런데, bar 함수 내에서 이 값이 예 상치 못하게 바뀐다면 문제가 된다. 하지만, 이 함수의 원형에서는 값이 바뀔 수도 있다라는 (왜냐하면 포인터로 넘겼으므로) 형태만 보여지고 있으므로 안심할 수가 없게 된다. 그래서 아진다면 const 키워드는 값이 바뀌지 않는다는 보장을 위해 사용된다. 위의 함수 원형이 다음과 같아진다면

void bar(const Foo* foo);

여기에서의 const 의미는 foo가 가리키는 곳의 값이 바뀌지 않는다는 것이다. 그래서 다음과 같은 시도는 컴파일러에서 에러로 처리된다. 

void bar(const Foo* foo) {

foo-> a = 1; // error;

즉, 포인터로 보내어도 값이 바뀌지 않는다는 것을 이 함수를 사용하려는 사용자에게 알리는 것이다. 흔하게 생각하는 오해가 이 경우와 같이 함수 원형에 const keyword를 사용하는 것이 이 함수를 작성하는 사람이 실수를 하지 않기 위해서, 즉, 함수 내에서 실수로 값을 바꾸는 경우가 생기지 않게 하기 위해서 라는 것인데, const를 쓰는 이유는 그게 아니고, 이함수를 사용하는 사람이 이 함수를 안심하고 쓰라고 하는 것이다. 

 

- UML로 Embedded System 프로그래밍 하기 (민현석 저) 中 에서 - 

'잡동사니' 카테고리의 다른 글

CAN 통신의 이해  (0) 2019.11.07
8비트 MCU 또는 32비트 MCU 선택 기준  (0) 2019.10.09
정수를 문자열로 변환하기  (0) 2019.06.27
#ifndef ~ #endif  (0) 2019.06.20
메모리 영역(code, data, stack, heap)  (0) 2019.06.20