GNEX의 Mobile C 에서의 포인터 사용
과거 GNEX에서 사용했던 Mobile C의 포인터에 대한 사용 예와 ANSI C 포인터와의 비교
GNEX 에서 사용되는 Mobile C…
요녀석이..
단말기 특성상 C의 모든 것을 지원해 주질 못합니다.
이 녀석에게 적응하려면 짜증나는 일들을 좀 많이 겪어야 합니다.
여러 가지가 있지만 그 중에서도 객체지향이 지원하는 다형성의 부재와 메모리 조작의 한계를 가장 먼저 들 수 있겠습니다.
앞의 객체지향 부분에 대한 불만을 이야기 한다면 GNEX의 Moblie C(이하 Gnex로 칭함)가 이름 그대로 C의 특성인
절차적 특성을 표준으로 삼고있는 절차식 언어이기 때문에,
Moblie C++을 만들지 않는 이상은 아무리 싫더라도 이 부분을 바꿀 수가 없습니다. 다른 플랫폼을 찾아봐야 되겠지요 ㅠㅠ
하지만 메모리 조작의 한계는 정말 치명적이라 일정한 수준의 노가다가 요구됩니다.
물론 지금은 512KB까지 메모리를 사용할 수 있으니,
이전 128Kb에 비하면 메모리에 대한 부담이 조금은 줄었다고 할 수 있겠습니다.
무엇보다 제가 알고있는 한도 내에서는..
GNEX는 동적 메모리 할당에 굉장한 제약이 따른다는 것입니다.
C를 바탕으로 이야기 했을때 단말기 특성상
1
2
typedef struct selftag{..}dataname;
tag struct_name = (dataname*)malloc(sizeof(dataname));
위와 같이 동적 메모리 할당을 컴파일러 자체적으로 막고 있다는 것입니다.
재미있는 것은 MallocInt()라는 정수형 사이즈에 인자값 만큼의 메모리 공간을 할당할 수 있는 함수가 지원이 되는데
이녀석은
1
2
3
4
5
6
7
8
9
10
11
12
void main()
{
string str;
int *ptr;
ptr = MallocInt(2);
*(ptr+0) = 1;
*(ptr+1) = 2;
MakeStr1(str,"%d",*(ptr+0));
DrawStr(10,10,str);
MakeStr1(str,"%d",*(ptr+1));
DrawStr(10,20,str);
}
위의 코드와 같이 부분적으로만 지원이 된다는 것입니다.
물론 [] 연산자를 사용한 포인터 접근도 지원이 안됩니다.
이런 경우 포인터를 사용하면 그만큼 소스의 가독성을 떨어뜨리는 일이 발생할지도 모릅니다.
무엇보다 MallocInt(); 함수의 리턴 타입이 int* 입니다.
void* 형인 C의 Malloc(); 과는 달라서 다른 캐스트연산자를 사용할 수도 없습니다.
강제로 형변환을 하려고 한다면 컴파일러가 이를 제지 합니다.
포인터를 사용한 메모리 조작의 범위를 단말기라는 환경의 특성상 C만큼 보장해 줄 수가 없다는 것입니다.
단적인 예로,
포인터에 상당한 제약이 따르는 GNEX에서 유일하게 링크드 리스트를 구현할 수 있는 방법이 있습니다.
링크드 리스트라고 해도,
완전한 링크드 리스트가 아닌 유사하게 만들어 사용하는 방법이라고 하는게 더 옳을 것입니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
struct object{
int x;
int y;
struct object *next;
};
void main()
{
string str;
struct object node, node2, *head;
node.x = 10;
node2.x =20;
head = &node;
node.next = node2;
MakeStr1(str,"%d",head->x);
DrawStr(10,10,str);
MakeStr1(str,"%d",node2.x);
DrawStr(10,20,str);
head = node.next; //head가 node2를 가리킨다.
MakeStr1(str,"%d",head->x);
DrawStr(10,30,str);
Flush();
}
구조체 포인터 head가 node를 가리키고 있다가 node2를 가리키고 있는 것입니다.
본래대로 라면, current로 가리키고 head는 첫 노드를 가리켜야 정상이겠지만
간략하게 설명을 하기 위해서 위와 같이 코딩하였습니다.
위 코드에도 치명적인 단점이 있습니다.
링크드 리스트의 최대 장점인 동적메모리 할당을 이용한 메모리 관리의 유연성의 부재가 그것입니다.
위의 코드를 보면, malloc이 지원되질 않기 때문에 필요한 만큼 그때 그때 구조체를 생성해서
이것을 구조체 포인터로 가리키는 방식을 따르고 있습니다.
위의 방식대로 코딩했을 경우에는 추가적으로 메모리 낭비도 심하게 일어날 소지가 다분하다는 것입니다.
따지고 보면 결국 링크드 리스트가 아닌, 링크드 리스트와 비슷하게 만드는 것입니다.
다행히 (*head).x 와 같이 -> 연산자를 풀어쓴 연산이 허용되기 때문에 부분적으로 필요한 부분에서
원하는 만큼의 가독성을 이끌어낼 수 있을 것입니다.
요컨대, 조금이라도 더 편하게 코딩을 하기 위해서,
제한적인 포인터를 하나라도 더 써먹기 위해, 고생을 하는것 보다는
간단하게 구조체 배열을 생성하여 사용하는 것이 더 나을지도 모를 일 입니다.
구조체 배열로 미리 메모리를 저장할 공간을 확보해 놓고서,
포인터로 접근하는 좋은 방법이 지금 이 글을 읽고 계신 분들의 머릿속을 막 스치고 지나가고 있을 것이라 생각합니다.
제가 글을 통해 이야기 하려고 하는게 바로 그 부분 입니다.
어차피 동적 메모리 할당을 통한 메모리 유연성을 확보할 수 없다면,
메모리를 좀더 간편하게 관리하고 조작이라도 해야 할 것입니다.
방금 말한 위와 같이만 메모리를 조작할 수 있어도 우리 프로그래머 입장은 굉장히 유리한 쪽으로 돌아가게 될 것입니다.
그러나 아쉽게도 GNEX에서는 포인터가 제한적이라 포인터로 연결해서 인덱스 연산을 수행할 수 없습니다.
1
2
3
struct object array[10], *ptr;
ptr = array;
(ptr+1)->x = 10;
위와 같은 연산을 수행할 수 없다는 것입니다.
이래도 정말로 포인터를 사용하고 싶다면 선택지는 두 가지 입니다.
구조체 배열로 한번에 메모리를 확보해서 배열 인덱스 연산으로 메모리에 임의적인 접근을 하는 방법과,
제일 위에서 언급한 것 처럼 일일히 그때 그때 필요할 때 마다 구조체를 선언하고 이것을 포인터로 연결해서 사용하고 필요 없을때 해제하는 방법입니다.
다만 귀찮은 문제는 둘째 치더라도, 스태틱 형을 지원하지 않기 때문에 주소 값을 넘기고 받고 해야 하는 문제가 발생하는데 함수 파라미터 값으로 포인터형을 선언할 수 없기 때문에 사실상 전역으로 구조체를 선언하지 않는 이상 데이터 손실이라는 치명적인 결함에 노출될 수 밖에 없습니다. 한마디로 요약하자면 “안돼니 하지 마라”와 같습니다.
첫번째 방법인 구조체 배열로 한번에 메모리를 확보해서 배열 인덱스 연산을 수행하는 방법의 단적인 예로는
1
2
3
4
5
6
7
8
struct object array[10];
int i;
for(i = 0 ;i<10 ; i++)
{
array[i].x = i;
array[i].y = i+10;
}
위와 같은 코드가 있습니다.
위에 제가 언급한 코드를 모두 이해하신 분들이라면, 비단 GNEX뿐 아니라 C 자체에서도
1
(array+1).x = 10; //error
위와 같은 코드에서 에러가 난다는 것.
구조체명은 배열명과는 다르기 때문에 인덱스 연산이 허용 되질 않는다는 사실을 알고 계실 것이라 생각합니다.
덧붙여 GNEX의 제한적인 부분을 단적으로 보여주는 예가 하나 있습니다.
1
2
3
4
5
6
7
8
9
#define Max 10;
struct object array[Max];
int i;
for(i = 0 ;i<Max ; i++)
{
array[i].x = i;
array[i].y = i+10;
}
위의 코드는 에러가 납니다.
#define 문으로 정의한 상수 값이 구조체 배열 선언 명령문의 첨자값 부분과 for문의 조건문 안에서 충돌하여 오류를 일으키기 때문입니다.
위의 모든 내용을 요약하자면,
GNEX의 Mobile C는 모바일 플랫폼 특성상 많은 제약이 있는 절차식 프로그래밍 언어이며,
JAVA를 따르는 Mobile 플랫폼에서 JAVA의 특성중 하나인 객체 지향을 지원해 주는것과는 달리
MOBILE C에선 C의 특성중 하나인 포인터를 지원해 주지만, 이에 제한이 있다.
메모리 조작 방법중 하나인 링크드 리스트를 흉내내서 (비단 링크드 리스트 뿐만 아니라 트리나 큐 구조 모두)
메모리 조작에 편의성을 두려는 시도를 생각해 볼 수도 있겠지만,
어차피 동적 메모리 할당에 크나큰 제약이 있으니
구조체 배열을 선언해서 사용하는 방법이 제일 속편하다.
과정에서, define 상수가 제대로 작동하지 않는 부분과 같은 제약이 있으니
이를 주의해야 한다 라고 할 수 있겠습니다.
마지막으로 포인터의 포인터 역시도 허용이 안된다는 점, 참고 하세요.
만약 내용 중에 잘못된 부분이 있다면
제 블로그의 포스트의 댓글이나 방명록에 글 남겨 주세요~!!