달력

52024  이전 다음

  • 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

어셈블리언어에는 특정 코드 위치로 바로 이동하기 위해서 goto 키워드를 많이 사용한다.
C언어에서도 goto 키워드가 존재하지만 절차식의 C 프로그래밍을 배우면서
흐름을 깨기 때문에 사용하지 않기를 권장한다.

유닉스를 만들기 위해 개발한 언어가 C인데, 정작 유닉스 & 리눅스 코드에서는
goto 키워들 상당 부분에서 발견할 수 있다.
간단히 말해서 엉키지 않게 제대로만 쓰면 error 처리하는 용도로 편리하게 이용할 수 있다.
다시 말하면 함수의 알고리즘의 흐름 상에서 다수의 error 검출을 해서 주로 공통적인 error 처리를 하는 경우,
함수의 끝에 error를 위한 특정 코드를 추가하고 Label을 설정해서 goto 키워드를 이용하는 방식이다.

이러한 goto 대신 비슷하게 사용할 수 있는 방식이 do while을 이용하는 것이다.

do{
  if (err)
      break;
}while(0);
error_handling_code

위와 같은 방식으로 while() 안에 조건을 '0'으로 하여 한 번만 실행되게 하고,
블록 안에서 error가 발생한 경우에만 블록을 빠져나오게 하여 while 이후의 코드를 실행하게 하는 것이다.

본인이 생각컨대 goto 키워드를 굳이 써서 다른 기능을 구현하자면,
다수의 Error에 대해서 그 처리가 공통적이지 않은 경우 여러 개의 Label과
해당 Label로 이동하게 하는 goto 키워드를 써서 구현하는 것이다.
이 경우 절차식 처리에서 상당히 벗어나는 어셈블리언어식 코드가 되는데
엉키지 않도록 주의해서 사용해야 할 것이다.

쓸데 없는 얘기를 많이 한 것 같은데,
결론적으로 goto 키워드를 통한 error 처리 대신에 do while(0)을 쓸 수 있다는 것을 알아두자는 것이다. ^^;

'Lang, Tool, Env' 카테고리의 다른 글

C 프로그래머가 알아야 할 것들  (1) 2007.05.16
CRC를 통한 error 검출  (0) 2007.05.16
static 통한 접근 제한  (0) 2007.05.16
CONTAINING _RECORD macro  (0) 2007.04.26
크기 미지정 배열의 새로운 고찰  (0) 2007.04.19
Posted by neodelicious
|

흔히 함수 내의 static 키워드는 함수 내에 선언된 변수에 대해서
해당 함수가 종료되어도 다시 함수가 실행되었을 때 그 값을 유지하도록 하기 위해서 사용한다.
참고적으로 이를 통해 해당 함수가 몇 번 호출되었는지 알 수 있다.

하지만 여기서 말하고자 하는 것은 함수 밖에 선언되어
외부 변수 혹은 전역 변수로 불리는 변수에 static이 적용된 경우이다.
이 경우 일반 전역 변수처럼 초기화를 하지 않아도 '0'으로 초기화 되며,
모든 함수에서 접근이 가능하다.

하지만 이때 접근 가능한 영역은 일반 전역 변수와 달리 해당 파일 안으로 제한된다.
참고적으로 다른 파일에 선언된 일반 전역 변수는 extern 키워드를 통해서 접근 가능하다.

이제 본론(?)이다.
이러한 static을 함수에도 적용할 수 있다.
함수 앞에 static 키워드를 붙이면 특정 파일에서만 사용하는 함수를 만들 수 있다.
static void _init();
위와 같은 형식이다.
선언과 정의에 모두 static을 붙여 놓고 이용하면 다른 파일에서는 접근 할 수 없다.
이를 이용하면 다른 파일에 같은 이름의 함수가 있더라도
해당 파일에서만 이용하는 특정 함수를 만들 수 있다.
추가적으로 내부적으로만 이용하는 함수의 이름에는 주로 앞에 '_'를 붙인다.

참고적으로 wiki에 있는 static 내용이다.
OOP 쪽의 개념도 설명해서 위의 내용과는 좀 다른 느낌이다.
http://en.wikipedia.org/wiki/Static_variable

'Lang, Tool, Env' 카테고리의 다른 글

CRC를 통한 error 검출  (0) 2007.05.16
do while(0) 을 이용한 goto 대체  (0) 2007.05.16
CONTAINING _RECORD macro  (0) 2007.04.26
크기 미지정 배열의 새로운 고찰  (0) 2007.04.19
Verify()  (0) 2007.04.14
Posted by neodelicious
|
구조체안의 특정 field의 주소를 통해서 그 field를 포함하고 있는 실제 구조체(객체)의 주소를 얻을 수 있는 방법이 있다. 그때 쓰는 매크로가 CONTAINING_RECORD 매크로이다.

우선 CONTAINING_RECORD 매크로처럼 유용한 매크로를 먼저 소개하면, offsetof 라는 매크로가 있다.
이를 이용하면 특정 구조체에서 해당 field의 offset을 알 수 있다.

offsetof 매크로에서 (unsigned long)(&((type *)0)->field)  뜻은, 0을 주어진 type의 시작점이라고 가정하도록 타입캐스팅을 한 후에, 그 type의 field를 엑세스 하여, type의 시작점부터 field 까지의 거리를 구하는 것이다. 즉, 0부터 field까지 offset이 얼마냐를 구하는 것이다.
(바로 위 문단, 3줄은 데브피아 링크의 김희준 님의 글을 조금 수정하여 인용한 것입니다.)

위의 offsetof 매크로의 기능을 그대로 이용한 것이 CONTAINING_RECORD 매크로이다.
이 매크로에서는 구조체 안의 특정 field에 대해서 구조체 시작부터의 offset 값을 구하고,
이를 실제 그 구조체 객체에서 그 field 주소에서 offset 값만큼 작은 주소로 이동한 값을 계산하여
구조체 객체 자체의 주소를 얻는다.

특히 이는 구조체 안에 리스트 구조가 포함되어 있을 때 유용하다.
리스트를 포함하는 구조체의 이름만 안다면 특정 리스트의 시작 주소를 넣어서 그 리스트를 포함하는 실제 구조체 객체의 주소를 얻을 수 있는 것이다.

아래 코드는 직접 실행해서 이를 확인한 것이다.
코드 실행 결과처럼 offsetof와 CONTAINING_RECORD 매크로 둘 다 잘 동작한다.
위에서 설명했듯이 (type *)0에서 0을 시작점으로 하기에,
코드에서 (testStr*)3 라고 해서 3을 시작점으로 할 수도 있음을 확인할 수 있다.
참고적으로 CONTAING_RECORD의 offset 부분에 offsetof 매크로를 넣어서 매크로를 구성할 수도 있다.

끝으로 CONTAING_RECORD 매크로에서 offset 부분 앞에 (unsigned long)을 붙였는데,
(unsigned long *)라고 되어 있는 자료도 봤다.
Visual Studio 6.0에서 테스트 해보니 둘 다 제대로 된 값은 나오는데,
(unsigned long *)라고 했을 경우 incompatible types 의 compile warning이 뜬다.


#include 
#include 

#define offsetof(type, member)   (size_t)&(((type *)0)->member)
#define CONTAINING_RECORD(address, type, field) \
		((type *)((char *)(address) - (unsigned long)(&((type *)0)->field)))

typedef struct _testStr{
	int a;
	char b[6];
	char c;
}testStr, *ptrTestStr;

int main()
{
   ptrTestStr ptr1 = (ptrTestStr)malloc(sizeof(testStr));
   char * bPtr = ptr1->b;

   printf("ptr1		: [%x]\n", ptr1);
   printf("&ptr1->c	: [%x]\n", &ptr1->c);
   printf("offsetof(testStr, c) : [%d]\n\n", offsetof(testStr, c));

   printf("(testStr*)0	: [%d]\n", (testStr*)0 );
   printf("(testStr*)3	: [%d]\n", (testStr*)3 );
   printf("(testStr*)3->c	: [%d]\n\n", &((testStr*)3)->c );

   printf("bPtr : [%x]\n", bPtr);
   printf("CONTAINING_RECORD(bPtr, testStr, b) : [%x]\n\n", \
                              CONTAINING_RECORD(bPtr, testStr, b));

	return 0;
}

사용자 삽입 이미지
Posted by neodelicious
|
C에서 배열은 포인터와 혼용되면서 많은 혼란을 가져오는 특징 중의 하나이다.
그러한 배열을 선언할 때는 그 크기를 지정해 주어야 하며
다만 배열을 선언하면서 동시에 그 값을 채워줄 때는 크기를 지정하지 않아도 되는 것으로 알고 있었다.
그래서 char a[] = "ab"; 는 가능해도 char a[]; 는 에러가 된다.

그런데 구조체 속에 선언한 크기 미지정의 배열은 어떻게 될까? 에러일까?
정답부터 말하면 가능하다.

좀더 말해서 해당 구조체 메모리 할당시 구조체 안에 크기 미지정의 배열에 대한
메모리는 할당되지 않는 것 같다.
이는 구조체에서 이 크기 미지정의 배열 대신 포인터를 넣으면
포인터 크기, 주로 4byte가 잡히는 것과 비교할 때 특이한 점이라 할 수 있다.
이러한 특징을 이용해서 구조체의 가장 끝 필드(Field)값으로 이러한 크기 미지정 배열을 넣고,
이 구조체에 대한 메모리를 할당할 때 구조체 자체의 메모리 크기에 더해서
해당 배열의 타입(type)의 배수 크기로 추가적인 메모리를 잡으면
그 배열 이름과 인덱스(index)로 추가된 메모리를 접근할 수 있다.
이 경우 구조체 메모리를 할당할 때마다 해당 배열의 크기를 원하는 만큼으로 조절할 수 있는 장점이 있다.

아래 코드는 이를 확인하기 위해 작성한 것으로서
특이한 사항은 추가적인 메모리를 할당하던 안 하던 크기 미지정 배열의 주소가
int 변수의 주소값에서 4만큼 큰 주소값이라는 것이다.
메모리는 할당되지 않지만 주소값은 있는 것처럼 보이는 것이다.



#include <stdio.h>
#include <stdlib.h>


typedef struct _T1{
int a;  // 4bytes
char * ch; // 4byte
}T1, *PT1;


typedef struct _T2{
int a;  // 4bytes
char ch[]; // 0byte
}T2, *PT2;


int main()
{
PT1 p1 = (PT1)malloc(sizeof(PT1)+sizeof(char)*5);
PT2 p2 = (PT2)malloc(sizeof(PT2)+sizeof(char)*5);
PT2 p3 = (PT2)malloc(sizeof(PT2));

printf("T1 size : [%d], T2 size : [%d]\n", sizeof(T1), sizeof(T2));
printf("sizeof(int) : [%d]\n\n", sizeof(int));

// error - a->ch[0] = 'A';
p2->ch[0] = 'B';

printf("p1 : [%x], &p1->a : [%x], &p1->ch : [%x]\n\n", p1, &p1->a, &p1->ch);

printf("p2->ch[0] : [%c]\n", p2->ch[0]);
printf("p2 : [%x], &p2->a : [%x]\n", p2, &p2->a);
printf("&p2->ch : [%x], &p2->ch : [%x], &p2->ch[0] : [%x]\n", &p2->ch, p2->ch, &p2->ch[0]);

printf("\np3 : [%x], &p3->a : [%x]\n", p3, &p3->a);
printf("&p3->ch : [%x], &p3->ch : [%x], &p3->ch[0] : [%x]\n", &p3->ch, p3->ch, &p3->ch[0]);

return 0;
}

사용자 삽입 이미지

Posted by neodelicious
|

Verify()

Lang, Tool, Env 2007. 4. 14. 18:32

해당 코드의 값이 참이라고 생각하는데 실제로 그런지 확인하고 갈 때
이용하는 것이 assert() 매크로이다.
관련 설명은 wiki 링크 자료를 참고하길 바란다.
http://en.wikipedia.org/wiki/Assert

그런데 debug 모드에서는 assert() 매크로를 쓸 수 있지만
release 모드에서는 이렇게 할 필요가 없다.
따라서 이를 구분하기 위해서 다음과 같이 verify() 라는 매크로를 쓸 수 있다.

#ifdef DEBUG
#define VERIFY(exp) ASSERT(exp)
#else
#define VERIFY(exp) exp

Posted by neodelicious
|

보호되어 있는 글입니다.
내용을 보시려면 비밀번호를 입력하세요.

사용자 삽입 이미지

다시 체계적으로 배우는

C 언어 포인터



예전에 도서관에서 빌려다 읽었던 책인데, 오늘 서점에 갔다가 눈에 띄어서 추천(?) 삼아 글을 올려본다.

내 기억으로는 일부는 굳이 그럴 필요가 없는데 권장하는 부분도 있었다.

그런데 대부분 포인터에 대해서 설명을 잘 해 놨던 것으로 기억한다.

<책 겉표지 그림 출처 : yes24>

yes24 링크
Posted by neodelicious
|

fgets() 사용 시 유의점


fgets() 이용할 때 new line('\n')에서 끊어지기는 하나
new line까지 저장되고 Null('\0')이 삽입된다.
따라서 new line이 저장된 부분에 Null을 저장하여 지우는 것이 필요하다.
하지만 만약 버퍼의 크기를 넘어갈 경우
항상 버퍼 크기 -1 크기만큼만 저장되고 마지막에 Null이 삽입된다.
따라서 버퍼 크기 -1 크기만큼 문자열을 입력하고 엔터를 입력하면
new line은 저장되지 않고 마지막에 Null이 삽입되어
이 때는 new line을 없애기 위해 Null을 추가로 삽입하면 안 된다.


그래서 다음과 같이 마지막에 new line이 있는지 여부를 판단하도록 했다.


#define BUFFERSIZE 10
char buf[BUFFERSIZE];
fgets(buf, BUFFERSIZE, stdin);
stringLength = strlen(buf);
if ( buf[stringLength-1] == '\n')
  buf[--stringLength] = NULL;


코드에서 10 byte buf에 대해서
"delicious" 9자를 입력하면 new line 없이 Null이 들어가고
"deliciou" 8자를 입력하면 new line을 Null로서 덮어 없앤다.
물론 9자를 넘어가는 문자열은 모두 저장되지 않고 사라진다.
참고적으로 아무 것도 입력하지 않고 엔터를 입력해도 new line은 저장되고
위의 코드에서는 Null로 덮어 지우게된다.

fgets()에 대한 기본 설명은 아래 링크를 참조하자.

http://www.cplusplus.com/ref/cstdio/fgets.html

Posted by neodelicious
|