OpenGL ES 2.0 샘플 (Android ver.)

원래는 Android용 게임을 만들어 보려고 OpenGL Es 2.0을 이용하여 3D 및 스프라이트 등을 출력하는 기본 라이브러리를 만들고 있었다. 그렇게 시작한 것이 벌써 1년이 지났다.

하지만 시간에 쫓기다 보니 우선 순위에서 계속 밀리게 되었고 자칫하다가는 영원히 Android용 게임은 완성되지 않은 채로 프로젝트가 끝나지 않을까 하는 우려까지 하고 있다. 여전히 Android용 게임 제작은 전체 우선 순위에서 밀리고 있고(Java에 익숙하지 않은 탓과 Java로는 개인 라이브러리가 구축된 것이 없다는 것이 가장 큰 걸림돌이다) 끝까지 성공해낼 확률은 꽤 낮은 편이다.

이것은 가장 초기에 만들었던 출력 테스트 샘플이다. Cube가 제대로 도는지 확인 해본 것이고 shader로 vertex, diffuse color, texture 출력까지 만들어 본 것이다. 간단한 터치도 테스트 해보기 위해 터치를 한 위치에 cube가 돈다.

사용자 삽입 이미지
Android용 OpenGL ES 2.0 출력 샘플 (44K)

프로젝트가 1.5 이상용인데, 너무 구형 프로젝트인지 실제로 import를 해보면 프로젝트 파일 관련 에러가 난다. 에러가 난 메시지로 구글에 검색해보면 프로젝트 파일 고치는 법이 나오는데, 그 방법을 써서 수정하면 된다.

Posted by 슴갈

2011/12/01 23:04 2011/12/01 23:04
Response
No Trackback , 2 Comments
RSS :
http://avej.com/textcube/rss/response/37

Trackback URL : http://avej.com/textcube/trackback/37

Comments List

  1. 비밀방문자 2012/01/05 18:44 # M/D Reply Permalink

    관리자만 볼 수 있는 댓글입니다.

    1. 슴갈 2012/01/05 19:43 # M/D Permalink

      OpenGL과 이벤트는 서로 별개이니 관련이 없을 것 같고요, 현재의 예제에도 Activity가 받은 press / move 이벤트를 Renderer의 s_touch_x / s_touch_y 에 저장하고 있으니 게임에 사용하기에는 크게 무리가 없다고 봅니다.

      굳이 event listener로서 필요하시다면, GLSurfaceView에 onTouchEvent를 재정의 하시면 바로 받으실 수 있을 것 같네요.

Leave a comment
[로그인][오픈아이디란?]

동급생2 맵출력 샘플 (bada ver.)

여느 때처럼 아침에 일찍 일어 났다. 하지만 오늘은 토요일이고 모처럼 회사를 안 가도 되는 토요일이다 보니 갑자기 시간이 너무 많아졌던 것이다.

딱 하루짜리 프로젝트를 해 보자는 생각에 15년 전에 한글화 할 때 끄적였던 리소스들을 꺼내어서 간단한 맵 출력 샘플을 만들기로 하고 아침에 app 개발을 위한 manifest.xml 을 발급 받았다. 그리고 오전과 밤시간을 이용해서 만든 것이 아래의 결과물이다.

사용자 삽입 이미지
십 몇 년 전에, 게제동을 통해서 DOS용으로 pascal과 asm을 이용해서 만들어서 소스를 공개했던 것인데 이것을 bada 용으로 만들어 보았다. 언어도 pascal + asm에서 C++로 바뀌었고 구조도 완전히 다 바뀐 것이라 완전히 새로 만드는 기분이었다.

일단 bada app의 이야기인 app을 만든 방법은,

- 기본 C++ project에서 Form-Base app을 선택
- 처음부터 있는 기본 Form을 타이틀이 없는 Form으로 수정
- 더불어 UI 관련 xml은 삭제
- application.xml 에서 auto scaling 관련 옵션 제거
  (게임에는 사용하지 않는 편이 품질을 보장할 수 있음)
- manifest는 API 1.0 version으로 발급
  (더 많은 기기와 버전에서 동작하도록 하기 위함)
- Application에는 Timer와 관련 listener를 추가
- Application의 OnForeground에서는 timer를 ON
- Application의 OnBackground에서는 timer를 OFF 했습니다.
- Timer 이벤트가 발생할 때마다 10 ms의 간격으로 게임의 메인 루프를 실행

- 게임은 1995~6년도의 원작의 resource를 그대로 사용
  (ELF사에 저작권이 있는 부분입임)
- 당시 4-bit용 게임이었으므로 GetCanvasN()의 포맷인 ARGB8888로 리소스를 변경
- 터치 입력을 추가, 스크린의 4 방향의 가장자리를 누르면 유이가 움직임

bada용 동급생 2 맵 출력 샘플 (257K)

Posted by 슴갈

2011/11/06 02:22 2011/11/06 02:22
Response
No Trackback , No Comment
RSS :
http://avej.com/textcube/rss/response/36

Trackback URL : http://avej.com/textcube/trackback/36

Leave a comment
[로그인][오픈아이디란?]

대변 파이터 (bada 이식作)

이번에는 DOS -> Windows -> WIZ -> CANNOO를 거쳐 bada 플랫폼에도 대변 파이터를 이식해 보았다. (앱스토어에 올릴 수가 없으니 풀소스로 첨부)

해상도가 800x480으로 커졌기 때문에 CANNOO에서까지 써왔던 320x240용 리소스는 모두 교체를 했다. 예전의 DOS용 16컬러 데이터를 복원하였고 일부 대사도 현재에 맞게 수정하였다.

제일 어려웠던 것은, 키보드 전용 게임을 풀터치로 만드는 일이었는데 결과적으로는 재미없는 게임이 되고 말았다. 처음부터 풀터치로 기획되지 않은 게임을 풀터치에 올리려는 자체가 잘 못 된 것이다. 게다가 2인용도 불가능 해서 하나의 조작으로 둘 다 동시에 움직이는 키배치로 수정을 하였다.


사용자 삽입 이미지
bada용 대변 파이터 (569K)



Posted by 슴갈

2011/10/17 07:40 2011/10/17 07:40
Response
No Trackback , 8 Comments
RSS :
http://avej.com/textcube/rss/response/35

Trackback URL : http://avej.com/textcube/trackback/35

Comments List

  1. 이정훈 2011/11/01 12:49 # M/D Reply Permalink

    안녕하세요
    정말 좋은 소스 감사합니다
    사막에서 물을 만난것 같습니다^^ 전체를 이해하진 못해도 부분부분
    감동일 뿐입니다

    opengles 의 glfixed 를 잘 알지 못해서 TextureCoord 하는 부분이
    좀 낯설었습니다

    openglES 에서 glFloat 을 사용할 수 있는거 같긴 한데 glFixed 를 사용하면
    더 빠른건가요?

    1. 슴갈 2011/11/03 12:43 # M/D Permalink

      OpenGL ES의 full spec.을 지원한다면야 glFloat로 사용해도 결과는 동일하고요. 다만, 이 소스 코드는 부동소수 연산 H/W가 없는 기기에서도 구동되도록 설계되었기 때문에 glFixed를 사용하고 있는 것입니다. bada에서는 glFloat이나 glFixed나 별로 차이가 없었던 것으로 기억합니다.

  2. 이정훈 2011/11/03 15:24 # M/D Reply Permalink

    감사합니다 ~~~슴갈님
    게임제작 10주년 기념인 Avej 게임을 하면서 받은 감동은 말로 표현이
    않되던 군여..맨처음 5.1 인치 pc게임인 인디아나존스 게임을 하면서
    느꼈던 그 몰입감을 다시 느꼈습니다

    어딘가에 막히면 늘 슴갈님 게임과 코드를 보면서 영감을 얻곤 합니다

    1. 슴갈 2011/11/06 02:28 # M/D Permalink

      Avej를 그렇게 느껴 주셨다니 제가 도리어 감사합니다.

      Avej는 정말 표현하고 싶은 것이 많았기에 아직도 포기하고 있지는 않고요. 정훈님의 말씀 때문에 도리어 용기가 생기는군요. (요 몇 주가 1년에 몇 달 없는 '피가 끊는 기간'이라, 모든 일에 열정이 다시 생겨난 것 같습니다. 물론 이내 사그라 들겠지만요...)

  3. TT 2011/11/10 23:32 # M/D Reply Permalink

    천금같은 자료이긴 하나 일반적인 방법으로 폰에 넣는것은 무리인듯 하군요 ㅜㅡ

    1. 슴갈 2011/11/16 22:41 # M/D Permalink

      일단 bada 플랫폼이 C++로 만들어서 휴대폰에 올리기 편하기 때문에 bada로 한 것이고요. 원래 Windows에도 되던 코드를 포팅한 것이라 적은 노력으로 Windows Mobile에도 올릴 수 있지 않을까 생각됩니다. (어차피 각 OS별로 framework과 system쪽의 포팅은 꼭 필요한 것이니....)

  4. TT 2011/11/27 20:48 # M/D Reply Permalink

    아, 그게 아니라 제 휴대폰이 bada os이긴 하나, 삼성앱스나 t스토어에서 다운로드 한 것 외에는 구동할 수 없는 것 같아서요..

    1. 슴갈 2011/12/04 18:53 # M/D Permalink

      아.. 맞습니다. 그때 그렇게 말씀하셨지요. ^^
      사실 개개인끼리는 TestKit이란 것을 통해 서로가 만든 app을 공유할 수 있긴한데 설명이 좀 복잡해서 아무래도 그것도 좀 어렵겠네요.. -_-;;;

Leave a comment
[로그인][오픈아이디란?]

나머지 연산자에 의한 실수

아래와 같은 코드를 만들었다.

예로 들기 위해서 만든 것이라 별 의미는 없는 struct인데, time_stamp라는 값이 들어 왔을 때 이 값을 짝수이면 내부 변수에 -1을, 홀수이면 +1을 대입하도록 하였다. 그리고 다음과 같이 짝수는 2로 나눈 나머지가 0이라는 것을 이용해 switch - case 문을 만들었다.

struct Step
{
explicit Step(int time_stamp)
{
// 어떤 값을 2로 나누었을 때,
// 나머지 값은 0 또는 1만 가능하다.
switch (time_stamp % 2)
{
case 0: m_step = -1; break;
case 1: m_step = +1; break;
}
}
int m_step;
private:
Step();
};

이런 방식의 코드를 Test case를 만들기 위한 유틸리티로 집어 넣었고, 곧바로 정적 코드 분석 툴에 의해 위의 코드는 잠재적인 문제가 있다는 통보를 받았다. 지금 보면 당연한 것인데도 보고를 받고 문제를 알아차리는 데는 십 분 정도 걸렸다.


정답은, 특정 조건에서는 생성자에서 m_step에 값을 대입하지 않아서 가비지 값으로 남아 있을 수 있다는 것이고, 그것에 대한 직접 적인 원인은 time_stamp 변수가 음수일 때를 고려 하지 않았기 때문이다.

0 이하일 때 나머지 계산의 값을 보면,

time_stamp = 0, -2, -4, -6, -8, … 일 때는 0
time_stamp = -1, -3, -5, -7, -9, … 일 때는 -1

의 값이 된다. 따라서 이 경우는 위의 코드에서 case -1: 을 추가 해야만 정적 분석 툴에서 문제를 통보하지 않는다.


Posted by 슴갈

2011/08/07 21:12 2011/08/07 21:12
Response
No Trackback , No Comment
RSS :
http://avej.com/textcube/rss/response/34

Trackback URL : http://avej.com/textcube/trackback/34

Leave a comment
[로그인][오픈아이디란?]

극한 테스트

Embedded 기기의 양산 제품에 대한 SW 테스트는, 거쳐야 하는 관문이 꽤 많다.

보통은, 개발자에 의한 (1)Unit test, QA 팀의 (2)Integration test / (3)Stress test, 그리고 SW 인력의 손을 떠난 (4)Monkey test나 (5)극한테스트(가칭)가 있다. 그 중에서 극한테스트(가칭)의 경우는 나에게 잊을 수 없는 개발 경험을 준 적이 있다.

때는 5~6년 전 어느 날, 갑자기 양산 검증 쪽에서 급보가 날아 들었다.

고온의 불가마에서 TV가 제대로 나오는지를 테스트 하는 실험이었는데 20일 정도를 계속 켜 놓았더니 TV가 죽었다는 것이다. 시리얼로 덤프를 받아서 개발팀에 넘겼고, 문제를 일으킨 것은 나의 코드였다. 양산 검증 막판에 이런 식의 에러가 발생하면 생산 라인이 가동을 멈추어야 하기에 이것은 꽤 큰 일이었다.

급하게 문제의 소스를 확인 해보니 다음의 위치였다.
(방금 창작한 코드라 당시의 코드와 조금은 다를 수 있다)

if (dst_alpha > 0)
{
unsigned long src_rate = (src_alpha *                                  (MAX_ALPHA_VALUE - dst_alpha)) / MAX_ALPHA_VALUE;
unsigned long last_rate  = src_rate + dst_alpha;

assert(src_rate < 256);
assert(last_rate < 256);
assert(last_rate > 0);
}

양산 제품에 왜 assert()가 빠지지 않았냐는 일단 차치하고(최종 버전을 릴리즈 모드로 하지 않은 듯 하다) 어떤 상황에서 assert()에서 죽을 수 있는 지를 검토해 보았다.

다음과 같은 검증 코드를 만들어서 돌렸고, 모든 유효한 값을 다 넣어 봐도 assert(false)는 일어 날 수 없다는 것을 증명하고서야 나의 혐의는 풀렸다.

void probe(unsigned long src_color, unsigned long dst_color)
{
#define MAX_ALPHA_VALUE 255

unsigned long src_alpha = (src_color >> 24);
unsigned long dst_alpha = (dst_color >> 24);
if (dst_alpha > 0)
{
unsigned long src_rate = (src_alpha *                                  (MAX_ALPHA_VALUE - dst_alpha)) / MAX_ALPHA_VALUE;
unsigned long last_rate  = src_rate + dst_alpha;

assert(src_rate < 256);
assert(last_rate < 256);
assert(last_rate > 0);
}
}

테스트

for (unsigned long s = 0; s <= 0xFF; s++)
for (unsigned long d = 0; d <= 0xFF; d++)
{
unsigned long src_color = (s << 24);
unsigned long dst_color = (d << 24);
probe(src_color, src_color);
}

정상적인 상황에서는 절대 발생할 수 없다는 것이 쉽게 증명이 되는 상황이었기에 다행인 것이지, 만약 이런 것을 증명하기 애매한 경우라면, 며칠을 밤을 새워 가며 시달렸을지도 모른다.

이 경우는 극악한 환경에서의 CPU 오동작과 관계가 있었겠지만, 이런 것 이외에도 memory cache의 타이밍 문제나, LCD controller의 전송 특징과 같이 S/W와는 직접적으로 관련 없어 보이는 것도 S/W의 문제로서 처리해야 하는 경우가 많다. 그리고 우리가 그런 문제점을 통보 받게 되면 S/W에서 처리 가능한 문제가 아니라는 것을 증명하기 위래 아까운 시간을 낭비하게 된다.

그나마 다행인 것은, 이런 문제를 많이 접하면 접할수록 과거의 경험을 통해 S/W의 문제가 아니라는 증명을 그나마 빨리 해낼 수 있게 되었다는 점이다. (물론, H/W device나 compiler를 의심하는 것은 S/W 엔지니어가 가장 마지막에 검토해야 하는 것이다)

Posted by 슴갈

2011/06/29 21:51 2011/06/29 21:51
Response
No Trackback , 4 Comments
RSS :
http://avej.com/textcube/rss/response/33

Trackback URL : http://avej.com/textcube/trackback/33

Comments List

  1. 용맨소녀 2011/06/30 01:41 # M/D Reply Permalink

    후덜덜.. 무서워요..

    1. 슴갈 2011/07/02 16:51 # M/D Permalink

      웹 서비스나 온라인 게임이면 그래도 패치라도 되는데 embedded device는 아무리 업데이트를 해주더라도 최초 출시 바이너리로 그대로 고수하는 경우가 대부분이라 이런 테스트를 하는가 봅니다. 사실 여기서 진짜 자신의 실수가 발견되는 것이라면... 아주 끔찍하네요... ^^ (책임을 직접 묻지는 않습니다만... 엄청난 심적 부담을 줍니다)

  2. ParkIncheol 2012/01/11 13:39 # M/D Reply Permalink

    안책임님, 우연히 웹서핑하다가 블로그를 발견했습니다.

    ^-^

    오늘도 좋은하루 보내세요.

    1. 슴갈 2012/01/13 13:48 # M/D Permalink

      흑 T_T .. 이러니 회사 욕도 못하고... (이미 제 개인 정보와 블로그는 공공재나 다름 없는)

      그럼, 오늘도 무사히!!!

Leave a comment
[로그인][오픈아이디란?]

switch 문의 최적화

내가 하는 일 중에는 API를 만드는 일도 한다. 그러다 보면 return type이 스펙에 맞게 제대로 되었나를 알아 보기 위해 negative test에 대한 test case를 만든다.

그 중에 enum의 경우는 사용자가 악의를 가지고 범위에서 벗어난 파라미터를 줄 수가 있으므로 그것에 관해서도 체크를 해야 한다. 예를 들어, A와 B만들 가지는 enum에 대해 강제로 음수나 아주 큰 양수를 보내는 행위 등을 막기 위함이다.

그래서 enum에 대해 허용된 값만을 넘겼는지는 체크하기 위한 다음의 매크로를 만들었다.

// 2개의 field를 가지는 enum 타입에 대한 값 체크
#define CHECK_INVALID_ENUM_X2(source, case1, case2) \
switch (source) \
{ \
case case1: \
case case2: \
break; \
default: \
return false; \
}

// 3개의 field를 가지는 enum 타입에 대한 값 체크
#define CHECK_INVALID_ENUM_X3(source, case1, case2, case3) \
switch (source) \
{ \
case case1: \
case case2: \
case case3: \
break; \
default: \
return false; \
}

2개 또는 3개의 값만 가지는 emul에 대해 주어진 파라미터의 값이 허용된 값 안에 있는지를 알아 보고, 그렇지 않을 때는 false를 리턴하게 되어 있다. (실제로는 last error나 error log 등을 세팅하기 때문에 더 복잡하지만...)

사실 이 macro는 특별한 문제가 없다. 다만 앞으로 이야기할 아주 특수한 경우를 빼고 말이다.

문제의 상황을 만들어 보기 위해 간단한 enum 2개와 API를 만들어 보았다.

enum EA { EA_1, EA_2 };
enum EB { EB_1, EB_2, EB_3 };

bool SetSomething(EA a, EB b)
{
CHECK_INVALID_ENUM_X2(a, EA_1, EA_2);
CHECK_INVALID_ENUM_X3(b, EB_1, EB_2, EB_3);

// SetSomethingInternal(a, b);

return true;
}

유효한 값이 2개가 있는 EA라는 enum과 유효한 값이 3개가 있는 EB라는 enum이 있고, SetSomething()이라는 API는 제일 먼저 a와 b라는 이름으로 들어 온 파라미터의 유효성을 체크한다.

그리고 나는 이 매커니즘이 제대로 작동하는 가를 확인 하기 위해 다음과 같은 test case를 만들었다.

#include <stdio.h>

int main()
{
bool r1 = SetSomething(EA(-1), EB_1);
bool r2 = SetSomething(EA_1, EB(-1));

printf("r1 = %d, r2 = %d\n", r1, r2);
return 0;
}

아마도 쉽게 구할 수 있는 유명한 컴파일러에서는 대부분 r1 = 0, r2 = 0 이라는 결과를 돌려 받게 될 것이다. 하지만 조금 이름이 알려진 어떤 회사의 embedded compiler에서는 이 결과가 r1 = 1, r2 = 0 과 같이 나왔다. 다양한 컴파일러에서 작업해야 하는 일이 하나의 업이 된 지금, 이렇게 동작이 다른 컴파일러를 보는 것은 그리 이상한 일은 아니다. 다만 문제를 분석해서 아무런 문제가 없는 코드로 만드는 것이 조금 귀찮은 일일 뿐이다.


Test case가 특정 기기에서는 일부 fail이 나온다는 보고를 받고는  바로 디버깅 작업을 시작하였다. 그리고는 놀라운(?) 사실을 알아 내었다. 일부 컴파일러에서는 최적화 옵션에 따라서는 CHECK_INVALID_ENUM_X2() 매크로 자체가 아예 코드에서 빠진다는 것이다.

유효 값이 2개 밖에 없는 enum에 대해 그것을 switch 문으로 분기를 하면, 내부적으로 if - else 문으로 바꾸게 되고, 이때는 if 쪽이나 else 쪽이나 코드가 break 밖에 없기 때문에 코드를 삭제하는 만행(?)을 저지른 것이다. 다만, emul이 3개가 있는 경우는 if - else 로 바뀌지 않기 때문에 위와 같은 최적화의 대상이 아니라 제대로 동작하는 것이다.

이런 경우는 직접 부딪쳐 경우를 파악할 수 밖에는 없는 것이다. 그리고 최대한 에러 체크를 하기 위한 부분은 최적화에 안 걸리는 쪽의 코드를 짤 수 밖에 없다. 위의 경우도 MACRO를 if (!(A and B)) 문으로 치환하여 해결하였다.

Posted by 슴갈

2011/06/04 19:50 2011/06/04 19:50
Response
No Trackback , a comment
RSS :
http://avej.com/textcube/rss/response/30

Trackback URL : http://avej.com/textcube/trackback/30

Comments List

  1. 슴갈 2011/08/24 15:34 # M/D Reply Permalink

    회사 사람들이 찾아준 해법, (http://gcc.gnu.org/bugzilla/show_bug.cgi?id=44328) -fno-tree-switch-conversion 를 사용하면 된다고 한다.

Leave a comment
[로그인][오픈아이디란?]

대변 파이터 (CAANOO 이식作)

일전에 GP2X WIZ로 만들었던 대변 파이터 embedded 판을 CAANOO로 포팅을 하였다. CAANOO에 맞는 키 설정으로 바뀌었으며, 사용 버튼은 다음과 같다.

<1인용>
방향키: 캐릭터를 8 방향으로 움직임
B키: 일반 공격
A키: 특수 공격
Home키:종료

<2인용>
<player 1>
왼쪽방향키: 캐릭터를 8 방향으로 움직임
<자동> 일반 공격
L키: 특수 공격
Home키: 종료

<player 2>
오른쪽방향키:캐릭터를 8 방향으로 움직임
<자동> 일반 공격
R키: 특수 공격
Home키: 종료

사용자 삽입 이미지
CAANOO용 대변 파이터 (198K)

Posted by 슴갈

2011/05/29 20:20 2011/05/29 20:20
Response
No Trackback , No Comment
RSS :
http://avej.com/textcube/rss/response/32

Trackback URL : http://avej.com/textcube/trackback/32

Leave a comment
[로그인][오픈아이디란?]

또 다른 지식의 성전 (CAANOO 이식作)

일전에 GP2X WIZ로 만들었던 또 다른 지식의 성전 embedded 판을 CAANOO로 포팅을 하였다. 역시 Lore 성만 플레이 가능하며 사용 버튼은 다음과 같다.

방향키: 캐릭터를 4 방향으로 움직임
B키: 확인
A키: 취소
II 키: 메뉴
Home키:종료

사용자 삽입 이미지
CAANOO용 또 다른 지식의 성전 (309K)

(CAANOO 버전은 CAANOO의 LCD 특성상 윗 부분 몇 픽셀이 베젤에 가려집니다)

Posted by 슴갈

2011/05/29 19:55 2011/05/29 19:55
Response
No Trackback , No Comment
RSS :
http://avej.com/textcube/rss/response/31

Trackback URL : http://avej.com/textcube/trackback/31

Leave a comment
[로그인][오픈아이디란?]
최근에 WIZ의 후속 기기인 CAANOO가 생긴 덕에, CAANOO의 툴체인에 익숙해져 보자는 미명으로 WIZ로 만들었던 게임을 포팅하고 있다.

처음부터 SDL 등으로 했으면 좋았을 텐데, 개인적인 만족을 위해 굳이 GPIO를 쓰고자 했던 지라 지금 CAANOO로 포팅하는 데 많은 애를 먹고 있다.

CAANOO 의 포팅 이야기는 다음에 하고 이번에 40분 이상을 고생하게 만든 코드를 소개하고자 한다. (printf()라도 되면 금방 잡았겠지만, 실행하면 그냥 기기가 다운되는 디버깅 환경이었다)

문제의 발단은

int num_chunk = read(fd, &data, sizeof(data));
if (num_chunk < 0)
return;
num_chunk /= sizeof(data[0]);

라고 처음에 만들었던 코드를 다음과 같이 최적화를 한답시고 한 줄 줄인 것이 원인이었다.

int num_chunk = read(fd, &data, sizeof(data))
                / sizeof(data[0]);
if (num_chunk < 0)
return;

man page에 의하면 read()가 size_t를 리턴하니, 같은 리턴 타입인 sizeof()로 나누어도 문제가 없을 것이란 예상이었다. 그와는 또 다르게 read()가 실패하면 -1을 돌리는 함수라는 것을 알고 있었고, (음수 / 양수)가 되면 결과는 음수가 되리라는 기본 산수적인 생각도 했기에 위의 코드는 문제가 없다고 생각하고 넘어 갔던 것이다.

나눗셈을 하는 순서가 조금 다를 뿐이었지만 이 부분이 잘 못되었다고 생각하기가 어려웠기에 이 주위에서 printf()를 찍으면서 좀 많이 헤매었다.


결론적으로, 내가 man page에서 read()가 size_t를 넘긴다고 보았던 것이 나의 착각이었다. 실제 함수의 프로토타입은 다음과 같다.

ssize_t read(int fd, void *buf, size_t count);

size_t가 아니라 signed인 ssize_t였다. 결국은 signed int / unsigned int 이고, 이 경우는 강제로 앞 쪽의 signed int가 강제로 unsigned 로 캐스팅되어서 결코 음의 결과 값이 나올 수가 없게 된다. 6~7년 전에도 회사 코드에서 비슷한 문제가 있어서 실수한 적이 있는데 또 그 상황이 재현되었다.

이것과 완전히 같은 내용은 아니지만 signed 와 unsigned의 비교 방법도 참 특이(?)한데, 다음과 같이 비교가 진행된다는 것을 꼭 알아야 한다.

- 양쪽 타입 크기가 같으면 signed 쪽를 unsigned로 변환
- 양쪽 타입의 크기가 다르면 작은 쪽을 큰 타입으로 바꾼 뒤
  unsigned 쪽을 signed 로 변환하여 계산


그리고 여기서 재미 있는 것 한 가지...

#include <stdio.h>

int main()
{
{
int          a = -10;
unsigned int b = 10;

printf("-10 / 10 = %d\n", a / b);
}

{
short          a = -10;
unsigned short b = 10;

printf("-10 / 10 = %d\n", a / b);
}

return 0;
}

이것을 실행 시키면 어떻게 될까. 결과는 다음과 같다.

-10 / 10 = 429496728 /* int / unsigned int의 결과 */
-10 / 10 = -1 /* short / unsigned short의 결과. */

방금 말한대로라면 계산이 이상해져야 정상인데, 왜 short 일 때는 잘 되는 것일까? 왜 이런지 이유를 알고 싶으면 assembly로 결과를 보내어서 보면 알 수 있다.

Posted by 슴갈

2011/05/29 17:06 2011/05/29 17:06
Response
No Trackback , No Comment
RSS :
http://avej.com/textcube/rss/response/29

Trackback URL : http://avej.com/textcube/trackback/29

Leave a comment
[로그인][오픈아이디란?]

this에 대한 NULL 체크

나는 플랫폼의 API는 C++이 아닌 C가 되어야 한다고 주장한다. 하지만 어떠한 이유에서든 현재는 Open API가 C++인 플랫폼을 개발하고 있다.
 
Open API가 C++ 이다 보니 여러 가지 문제가 많긴 한데(장점도 있겠지만) 그 중에 하나는 NULL instance가 그 객체의 method를 부르려고 할 때 방어 하는 방법이다.

최종적으로 Open API의 제공자는 모든 경우의 사용자의 오남용을 막을 의무가 있기에 관련 method마다 제일 앞에 다음과 같은 코드를 넣고 있다.
 
void MyClass::SetValue(int value)
{
        if (this) // (1)
        {
               this->m_value = value;
        }
}
 
이렇게 했을 때, 다음과 같은 실수를 막을 수 있을 것이란 기대이다.
 
MyClass* pMyClass = CreateMyClass(…);
// NULL 체크를 하지 않았음
pMyClass->SetValue(100);
 
물론 생각대로 잘 동작하고 있었고 국내 모 전자 회사의 TV에는 이런 코드로 실제 애플리케이션의 실수를 막고 있다. –MIPS와 ARM을 사용-
 
하지만 문제는 휴대폰에서였는데, 여기에서는 이 경우 (1)이라고 표시한 부분에서 항상 프로그램이 죽는다 (죽는 이유는 좀 복잡하지만 그건 생략). 여기서 사용한 컴파일러는 ARM core용이긴 하지만 armcc와는 조금 다른 다른 부류이다. 일단 나는 표준 C++의 동작을 따르지 않는다는 이유로 컴파일러 버그로 통보해야 한다는 주장을 했지만, 담당자가 알아 보더니 C++ 스펙에서는 this가 NULL일 경우에 대한 참조는 보장할 수 없다는 것이 답이라고 한다.
 
C++을 사용할 때, virtual table의 구조라든지 method call과 관련된 구조를 숙지하게 되고 그 내용에서는 보통 this는 stack이나 register를 통해 전달 되기 때문에 위의 코드는 표준에서도 문제가 없다고 생각하기 마련인데 사실은 그렇지 않나 보다.


Posted by 슴갈

2010/12/29 23:18 2010/12/29 23:18
Response
No Trackback , 9 Comments
RSS :
http://avej.com/textcube/rss/response/28

Trackback URL : http://avej.com/textcube/trackback/28

Comments List

  1. ptptomr 2010/12/30 10:24 # M/D Reply Permalink

    오옷 오랫만의 posting이군요. 예를들어 주신 것과 같이, 빈번한 사용자 실수가 예상되는 case (missing of checking null pointer) 에 대해 platform이 방어할 수 있는 방법이 없다니 믿기지 않습니다.

    1. 슴갈 2010/12/30 13:34 # M/D Permalink

      아예 안드로이드처럼 VM을 도입하든지, 아니면 this 자체를 참조할 수 있도록 해주는 대부분의 컴파일러를 사용하면 문제는 없을 것 같습니다. 다만 표준이 아니라는 것일뿐, 대부분의 컴파일러는 위의 코드로 제대로 방어해 주잖아요.. ^_^

  2. doyongid 2011/01/19 08:30 # M/D Reply Permalink

    와우. 좋은 내용 감사합니당

  3. summerlight 2011/01/25 11:10 # M/D Reply Permalink

    가상 함수를 통한 간접 분기에서 this가 null이거나 해당 타입의 객체가 아니라면 적절한 vtable을 참조할 수 없어서 충돌이 일어납니다. 뭐 그렇다고 해도 거의 모든 컴파일러가 정적으로 결정되는 함수 호출에 대해서는 안전한 코드를 만들긴 하지만요.

    표준에서 이렇게 어디에서는 허용하고 어디에서는 안 하는 식의 예외를 두면 혼란스럽기 때문에 this == null인 경우는 일관되게 정의되지 않은 동작을 하도록 한 것으로 알고 있습니다.

    1. 슴갈 2011/01/30 15:31 # M/D Permalink

      virtual 함수이면 이미 진입 자체가 안될거고요.. NVI라면 진입이 될 것이며 그때 검사하려고 한 것이지만, 결과는 undefined이네요 -_-;; 문제의 경우는 RVCT에서 어떤 옵션(파악은 안 된)의 조합에서만 발생했습니다. 전혀 엉뚱한 주소에서 죽는 것으로 보아서는 객체를 관리/저장하는 방식이 좀 다른 것이 아닌가까지만 추측을 해보았습니다.

  4. summerlight 2011/02/08 12:13 # M/D Reply Permalink

    혹시 컴파일러의 명령어 재배치나 혹은 CPU의 비순차 실행 때문에 발생하는 오류 아닐까요? this가 항상 유효하다고 가정하면 수행 효율 증대를 위해 if (this) 내부의 구문부터 투기적으로 수행을 시켜볼 수도 있는데, 여기에서 this가 NULL이라 미정의라능! 하면서 배를 째는 걸지도 모르겠습니다. 저도 예전에 명령어 재배치로 인해 발생한 문제로 (이건 멀티 쓰레드 문제였지만) 고생했던 적이 있습니다. -_-;

    1. 슴갈 2011/03/09 22:07 # M/D Permalink

      ARM이 과연 그럴지는 의문인데요... 그 이후로도 가끔씩은 한 번 원인을 파악해 볼까하는 생각도 들었지만 업무에 치이다 보면 그걸 실행에 옮기기는 좀 힘드네요.

  5. 비밀방문자 2011/04/10 02:08 # M/D Reply Permalink

    관리자만 볼 수 있는 댓글입니다.

    1. 슴갈 2011/05/10 20:07 # M/D Permalink

      ooo님의 미래에 긍정적인 역할을 하게 되었다면 저도 영광입니다. ^^

Leave a comment
[로그인][오픈아이디란?]
« Previous : 1 : 2 : 3 : 4 : Next »

블로그 이미지

GP2X WIZ와 CAANNO와 bada용 게임 개발을 하자

- 슴갈

Notices

Archives

Authors

  1. 슴갈

Recent Trackbacks

Calendar

«   2012/02   »
      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 27 28 29      

Site Stats

Total hits:
36065
Today:
14
Yesterday:
44