블로그 이미지
핑크대지

태그목록

공지사항

최근에 올라온 글

최근에 달린 댓글

글 보관함

calendar

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 30 31

'Tip'에 해당되는 글 21

  1. 2008.08.25 메모리 관리
  2. 2008.07.07 Source Code

메모리 관리

2008. 8. 25. 10:15 | Posted by 핑크대지
1. 개요

이 문서는 ESP-NS에서 동작하는 응용 프로그램을
작성할때 메모리 할당과 해제및 메모리 처리에
대한 주의점을 소개합니다.

작성자 : 유영창
frog@falinux.com
작성일 : 2004년 9월 17일
수정일 :

관련된 ADK( Application Developer Kit ) 디렉토리

adk/sample/check_index
adk/sample/assert


2. 임베디드에서 메모리 관련 문제

임베디드 시스템은 사용시간에 따라서 크게 두가지로
나누어 볼수 있읍니다.

첫번째는 필요한 경우만 전원을 넣고 동작시켜 사용하는 경우로
동작 시간이 짧은 경우입니다. (참으로 고마운 시스템입니다.)

두번째는 모니터링 시스템같이 지속적인 제어가 필요하여
1년 365일 전원이 절대로 나가면 안되는 경우 입니다.

프로그래머 입장에서 보면 첫번째 방식을 좋아 하게 됩니다.
이건 무정전 시스템에 사용되는 프로그램을 작성하신분들이라면
온몸으로 느끼는 감정입니다. ( 해본 사람들은 압니다. ㅜㅜ )


시스템이 무정전으로 동작한다는 것은 여러가지를 고려 해야 합니다.
그중 으뜸은 메모리 누수 입니다.

C 로 작성하는 프로그램은 반드시 메모리에 관련된 문제 때문에
한번 이상은 반드시 고생하게 됩니다.

더구나 C 언어에 익숙하지 않으신 분이라면 포인터 참조에 관련된
수 많은 버그로 엄..청..난... 고생을 합니다.

그래도

납품하기전에 메모리 관련된 버그 문제점을 알게 되면 그나마
다행입니다.

그러나 프로그래머 입장에서 두고 두고 속썩이는 것중 하나가
장기간 동작하다 멈추는 경우입니다.

프로그램을 수정해서 버그를 잡았는지 확인하려고 하면
몇일씩이나 걸리기 때문에 프로그래머들을 미치기 일보
직전까지 만듭니다. ( 대부분의 경우 어디에서 발생했는지도
잘 모르죠.. )

이런 경험을 여러번 하다보면 나름대로의 방법론이 생깁니다.

이런 경험과 관련되어 메모리를 다루는 방법에 대한 몇(?)가지와
메모리 할당과 해제에 관련된 함수를 소개하려고 합니다.


3. 배열을 사용하라....

PC 프로그램을 작성하시던 분들이 임베디드 시스템에서
프로그램을 작성할때 가장 걱정되는 습관 중 하나가
메모리 할당과 해제를 아주 좋아 한다는 겁니다.

PC 시스템에서 작성하는 프로그램은 실장된 메모리가
많기 때문에 메모리 할당과 해제를 이용하면
유연한 프로그램이 가능해 집니다.

그..러..나..

임베디드에는 메모리 할당과 해제를 자주 이용하는 습관은
절대적으로 말리고 싶은 것 습관중 하나입니다.

보통 제품을 설계하는 분들이 개발자들에게 요구하는 것중
하나가 만능제품이죠...

그런데 이 만능은 프로그래머가 만능이 되어야 합니다.
이런 경우에도 적용될수 있고 저런 경우에도 적용될수 있고

마음약한 개발자들은 이런 요구를 수용합니다.

그러다 보니 개발해야 하는 프로그램 구조가 요구 사항에
가변적인 구조를 가지게 되죠..

결국 메모리 할당 구조와 리스트와 같은 자료 구조를 사용하게
됩니다.

이때부터 개발자는 머리털 빠지기 시작합니다.
( 제가 속빈 인간이 된 사연이 여기에 있읍니다. )
리스트구조와 메모리 할당은 시스템의 버그 원인 순위에
가장 상위 순위를 차지 합니다.

오랜 연륜을 가지는 개발자들은 일단 이런 영업 요구에
적절히 대항합니다.

그리고 어느정도 제품이 필요한 요구 사항을 제한합니다.
그리고 그에 맞게 프로그램을 개발합니다.

이때 이 분들이 작성한 프로그램을 보면 ( 무정전 제품에
들어가는 프로그램일수록 ) 전역변수와 배열을 많이 사용하게
됩니다.

이 전역변수와 배열을 사용하는 것은
프로그램을 처음 배울때 회피하라고 들었던 것인데
의외로 고수일수록 많이 사용합니다.
(심지어 goto 문을 남발하시는 고수도 많습니다. )

배열을 사용한다는 것은 일정한 크기를 갖기 때문에
확장성에 용이하지 않을 것 같은데
환경 파일로 모든 확장성을 고려하는 것은 임베디드
제품에 크게 의미가 없읍니다.

이미 한정된 크기의 메모리를 가지고 있는 시스템에
확장할수 있는 크기를 가지도록 프로그램을 작성한다는 것은
의미가 없읍니다.


배열을 사용하게 되면 다음과 같은 장점이 있읍니다

1) 메모리 할당과 해제와 관련된 버그가 없다.
2) 메모리 할당과 해제에 소모되는 시간 소모가 없다.
3) 인덱스 방식의 참조가 가능하므로 잘못된 포인터
참조에 의한 버그 가능성이 작다.
4) 시스템의 메모리를 효율적으로 사용할수 있다.
5) 참조 속도가 매우 빠르다.
6) 프로그램 소스 코드가 직관적이다.
( 포인터 참조 연산자가 얼마나 어려운 코드 형식을
작는지 다들 아시죠? )

등등의 장점이외에 더 있지만 생각이 안 나는 군요...

어쩄든 임베디드 장비에 사용되는 프로그램은 가급적 배열을
사용하시는 것이 좋습니다.

3. 가급적 상수를 매크로로 정의해서 사용하라

메모리 이야기에 왠 매크로 상수?

뭐 이렇게 궁금하게 생각하시는 분들이 있을 것 같은데...
이래야 고수 소리를 듣습니다. 소스에 숫자가 적게 보일수록 고수 입니다


예를 들어 보죠...

어떤 분은 프로그램을 이렇게 작성합니다.

char check_ids[300];

고수는 이렇게 작성합니다.

#define MAX_CHECK_IDS 300
char check_ids[MAX_CHECK_IDS];


이것은 나중에 확장성을 가지는 효과가 있고
접근하는 인덱스의 검사를 할 경우에 유용합니다.


또한 인데스 검사를 하는 경우에 유용합니다.
보통 프로그램을 작성할때 인덱스 접근에 대하여
다음과 같이 처리하면 좋습니다.


char get_date( int index )
{
#ifndef NO_CHECK_INDEX_FLAG
if( (index < 0) || (index >= MAX_CHECK_IDS ) )
{
dlp( "index over\n" );
exit(0);
}
#endif
return check_ids[index];
}

또는 아예 인덱스 검사를 하는 함수를 매크로로 만들어서 사용하는 경우도 있읍니다.

선언예)

#ifndef NO_CHECK_INDEX_FLAG
#define check_index(idx,max) {\
if( (index < 0) || (index >= MAX_CHECK_IDS ) ) \
{ \
dlp( "index over\n" ); \
exit(0); \
} \
}
#else
#define check_index(idx,max) {}
#endif


사용예)

char get_date( int index )
{
check_index(index,MAX_CHECK_IDS);
return check_ids[index];
}

3. 초기화를 꼭 하라

변수를 사용할때 특히 전역 변수를 사용할때
초기화를 하는 습관은 버그를 예방하는 효과가 있읍니다.
특히 포인터 형식의 필드변수를 포함하는 구조체가 있을 경우에는 특히나 그렇습니다.

초기화 값은 0으로 사용하는 것이 좋습니다.
포인터의

4. sizeof 함수를 즐겨 사용하라

메모리 복사나 초기화를 사용할 경우와 같이 배열이나 구조체의 크기를 구할 필요가
있을때 sizeof 를 자주 사용합니다.

귀찮아서 그냥 숫자를 주는 습관을 가진 분들이 있는데
이런 분들에게 sizeof 함수의 사용을 강력하게 권장합니다.

앞의 배열에서 초기화를 처리할때 다음과 같은 형식으로 처리하는 것이 좋죠...

memset( check_ids, 0, sizoef( check_ids ) );

복사할 경우에 역시 이런 식으로 사용하는 것이 좋습니다.
하지만 복사할 경우에 크기는 어느 것을 사용하는 것이 좋을까요?
권장하는 것은 앞에것을 사용 하는 것입니다

void copy_item( struct a *bdata )
{
struct a adata;

// 권장하는 예
memcpy( &adata, bdata, sizeof( adata ) );

// 별로 권장하지 않지만 좋은 예
memcpy( &adata, bdata, sizeof( struct a ) );


}


5. 포인터의 간접 인덱스를 사용할 때는 주의하라...

포인터 변수를 사용할때 포인터의 초보자들이 실수하는 큰 것
중에 하나는 다음입니다.
특히 하드웨어를 다룰때 많이들 실수 합니다.

char *app;
int *bpp;

app++;
bpp++:

이것은 1씩 증가 시키는 겁니다.
그런데 app 나 bpp 에 0x300000 이라는 주소값이 있다면
app는 0x30000 이 되지만 bpp 는 0x300004 가 된다는 것을
까먹습니다.

이것이 나중에 속썩일 경우가 많다는 점을 기억하십시오

(app+ 1) 과 (bpp+ 1) 도 마찬가지 입니다.
이런것은 매크로를 사용해서 선언할 경우 많이들 실수하는 겁니다.

예를 들어 하드웨어 레지스터를 접근하는 경우에

#define REG_A(x) (x + 1)
#define REG_B(x) (x + 2)

이런식으로 처리할때 매크로는 단순히 문자열 치환이기 때문에
위와 같은 문제가 발생할수 있다는 것입니다.


6. 스택변수 즉 로컬 변수를 조심하자

함수안에 선언하는 로컬 변수는 두가지 장점이 있읍니다.

선언이 간단하고 할당에 걸리는 시간이 없다는 것입니다.
그러나 이 로컬 변수는 버그의 온상이 되므로 주의할 필요가 있습니다.

예를 들어 다음과 같은 경우를 생각해 봅시다.

int test_func( char *tmpstr )
{
char buff[32];
int p;

p = strlen( tmpstr) ;

sprintf( buff, "ID:%s", tmpstr );
write_func( buff );

return p;
}

이 함수를 소스상에서 본다면 아무런 문제가 발생하지 않는 함수입니다.
그런데 이 함수는 두가지 문제점을 가지고 있읍니다.

만약 tmpstr 이 NUL 코드를 포함하지 않는다면?
또는 tmpstr 이 28 자 이상이 된다면 ?

아...

프로그램은 어떻게 동작할지 아무도 장담할 수 없읍니다.

다행히 세그먼트 폴트라도 발생해서 미리 알수있다면 좋지만
스택을 접근 할 경우에는 세그먼트 폴트가 잘 발생하지 않습니다.

경우에 따라서는 스택이 깨지기 때문에 엄한 곳으로 프로그램이 점프할수도 있고
다른 변수들이 수정될수도 있읍니다.

더구나 특별한 경우에는 두번 호출되어 도착한 함수가 꺼꾸로 리턴될때
중간 함수를 거치지 않고 리턴되거나 진행 루틴이 실행되지 않을 경우도 있읍니다.

또는 뒤에 선언된 변수들 값이 수정될수도 있읍니다.

이런 경우에는 스택 변수의 크기를 아끼지 않는 것이 가장 최선의 예방책입니다.
(물론 주의해서 작성하는 것이 더 큰 최선의 에방책이죠.. )

예를 들어 넘어온 크기보다 2 배나 3 배정도의 크기를 잡는 겁니다.

위의 경우에는 char buff[128] 정도로 선언하는 것이 안전합니다.

또는 버퍼의 뒷쪽에 임의 변수를 하나 두는 것도 요령인데
별로 추천은 하고 싶지 않군요


7. 자료구조를 사용한다면 검증된 라이브러리를 사용하라..

프로그램을 작성하다보면 배열보다 리스트와 같은 자료구조를
이용하는 것이 효율적일때가 있읍니다.

대표적인 것들이

스택이나 , 큐, 더블 링크드 리스트 , 리스트, 이진 트리 리스트
등등이 있읍니다.

이때 많은 분들은 직접 만들어 사용합니다.

그런데 이런 처리는 포인터를 사용해야 하고 저같이 논리에 약한
사람들이 만들면 버그가 살기에 좋은 환경을 제공합니다.

그래서 저는 인터넷에 관련 자료 구조용 공개된 소스를 이용하기를
권장합니다.

특히 소스포지에 가면 이런 자료 구조체 라이브러리들이 많이 있읍니다.
가급적 이렇게 공개되고 여러사람이 사용하고 있는 것을 이용하기를
바랍니다.

직접 만들면 피 봅니다... ㅜㅜ

8. 메모리 할당 함수들

C 에서 메모리를 할당하기 위해서 사용하는 함수들은 다음과 같습니다.

void *calloc(size_t nmemb, size_t size); // 할당 + 메모리 클리어
void *malloc(size_t size); // 할당
void free(void *ptr); // 해제
void *realloc(void *ptr, size_t size); // 재 할당 + 메모리 복사

이 중에서 가장 많이 사용하는 함수는

malloc 함수와 free죠...

하지만 malloc 함수보다는 calloc 함수를 사용하기를 권장합니다.
(저역시 malloc 함수를 자주 사용합니다만 ㅜㅜ )
그래도 malloc 함수를 자주 사용하게 되면 다음과 같은 처리를 꼭 해주시기를 바랍니다.

char *buff = NULL;

buff = malloc( 1000 );
if( buff != NULL )
{
memset( buff, 0, 1000 );
}
else
{
// 에러 처리 ( 보통은 프로그램을 죽인다. )
}

if( buff != NULL ) free( buff );


9. assert 함수


앞에서 malloc 함수를 처리할때 에러가 난 경우에 대한 처리가 귀찮죠?
이런 경우 사용하거나 기타 등등에 사용하면 좋은 함수가 assert 함수입니다.


이 함수는 #include <assert.h> 를 포함하고 사용하면 되는데
사용 문법은 간단합니다.

assert( 논리식 );

이 함수는 논리식이 참이면 특별한 것을 하지 않습니다.
그러나 거짓이면 에러를 표현 합니다.
파일명과 함수명 그리고 라인번호와 함께 문제가 된 값을 표현합니다.
그리고 프로그램을 죽입니다.( 으으 살벌.. )

보통은 포인터 변수의 값이 0이 되는 것을 방지하기 위해서 사용합니다.

assert 함수는 NDEBUG 가 정의 되어 있으면 아무런 것도 하지 않는
함수이므로 소스를 수정하지 않고서도 함수의 에러 처리를 무효화
할 수 있는 무척 좋은 함수 입니다.


[펌] http://kelp.or.kr/korweblog/write.php?sid=04/09/17/1292118

Source Code

2008. 7. 7. 13:52 | Posted by 핑크대지
What is Source?
소프트웨어 소스 코드란 프로그래머가 작성한 원래의 명령어들을 나타낸다. 이를 특정한 종류의 운영체제와 하드웨어상에서 최적화시켜 돌리기 위해서는 컴파일을 하게 된다. 그 결과 바이너리코드가 나온다. 소프트웨어란 보통은 바이너리 코드 배포폰을 가리킨다.

"closed source" 바이너리 코드를 갖고 어떻게 돌아가고 어떻게 수정하며, 포팅이나 재사용은 어떻게 하는지 알아낼 쉬운 방법이란 없다. 바이너리 코드를 가지고 무엇이든 하려면, 결국 개발자는 소스코드에 접근해야 한다.

이상적이라면야 그러한 소스코드도 접근 가능할 터이다. 무료로 공개되면서 통제가 잘 되고, 어떠한 부분이 어떠한 일을 수행하는지 문서도 완벽하게 되어 있다면 금상첨화이다. 그렇다면 여러가지 방법으로 코드를 사용하는 데에 장애물이 거의 사라질 것이다.

오리지날 소스코드를 잃거나 쓸 수 없는 상황이라면, 바이너리코드를 디컴파일하여 소스코드를 다시 구성하거나, 리버스엔지니어링으로 새로운 소스코드를 재작성할 수도 있을 것이다. 어느 쪽이건 재사용이나 소프트웨어 작업 구도를 알아내기에는 훨씬 더 복잡하고 어려운 방법이다. 소프트웨어 소스코드의 공개가 그리도 유용하다면, 개발자들은 어째서 항상 오픈소스를 지지하지 않을까?

Why Closed Source?

폐쇄형 소스 소프트웨어에 대한 논리를 거론할 수 있겠지만 여기에서는 어찌하여 바이너리 코드만을 선보이는지에 대한 세 가지 이유를 말해보겠다.:

  1. 경쟁자들이 자기 제품에다가 여러분의 소스코드를 갖다붙일 수 있다. 실생활에서 누군가가 당신의 디자인을 자기 것인 양 판매하려 한다면, 감옥가기 십상이다. 하지만 코드는 숨기기 쉽기에, 여러분의 코드를 경쟁자가 쓰고 있다고 말할 방법이 없다. 폐쇄형 소스 개발의 첫 번째 이유는, 장사기밀로서 저작권의 보호이다.
  2. 사용자가 제품을 수정한다면 지원하기가 더 어려워진다. 버그를 밝혀낸다거나, 의도되지 않은 방식으로 사용하여 깨뜨려 가면서 결국 해결해야 할 사람은 여러분이고 책임도 여러분이 져야 한다. 폐쇄형 소스 개발의 두 번째 이유는 특정 목적을 위해 디자인하되, 지원을 할 수 있는 제품 개발이다.
  3. 사용자들이 여러분의 원칙이나 바람과는 반대로 여러분의 제품을 선택적으로 적용시킬 수 있다. 어쩌면 경쟁 플랫폼에 더 가치를 안겨다줄 수도 있다. 폐쇄형 소스 개발의 세 번째 이유는 제품의 목적대로 하는 제품 통제이다