메뉴 건너뛰기

Hodol's Blog

Documents C 삽질노트[20150418]

2015.04.18 01:24

Hodol 조회 수:760

목차
strtok() 사용시 주의사항
strtok 함수는 첫번째 인자를 수정한다. 토큰을 찾으면 구분자를 NULL로 수정한다. 따라서 첫번째 인자로 const char* 형태의 상수 인자를 사용할 수 없으며 첫번째 인자의 메모리 주소가 가리키는 메모리 공간에 저장된 값이 파괴되는 것을 인지하고 이 함수를 사용해야 한다. 첫번째 인자의 값을 보존해야된다면 문자열 복사를 이용하여 복사본을 만들어 사용해야 한다.
마지막 구분자가 있다면 삭제하고 사용하는 것이 좋다. 엔터가 하나의 토큰으로 인식 되기 때문이다. 다음과 같은 간단한 성적 데이터 베이스를 파싱을 한다고 가정해보자. 과목1, 과목2, 순으로 시험을 치르며 아직 치르지 않은 시험의 성적은 (데이터 용량을 줄이기 위해) 기록하지 않는다고 하자. 따라서 각 레코드의 길이가 일정하지 않다.
// 이름;나이;과목1;과목2;...;과목N;
윤아;18;96;
태연;19;97;88;100;
서현;17;93;87;
위와 같이 저장된 파일에서 다음과 같이 fgets 함수를 이용하여 한줄 읽어왔다고 하자.
fgets(tempString,128,fp);
그러면 문자열 포인터 tempString이 가리키는 값은 윤아;18;96;\n 이다. 마지막에 실제로 보이지 않는 개행문자 \n이 숨어있는 것에 주의하자. 이 상태로 strtok(tempString,";") 함수를 사용을 하면 tempString으로 다음과 같이 4개의 토큰을 뽑을 수 있다.
{"윤아","18","96","\n"} // 사실은 윤아\018\096\0\n\0
결과만 놓고 보면 윤아는 18살이고 과목1의 성적은 96점을 받았고 과목2의 성적은 \n 점을 받은 것처럼 해석할 소지가 있다. 여기서 4번째 토큰인 \n은 우리가 원치 않는 값이다. (사실 \n은 데이터베이스의 ;와는 다른 두번째 구분자이다.) 따라서 우리는 이를 피하기 위해 마지막의 \n을 삭제해줄 필요가 있다. 또한, 경우에 따라서는 마지막 개행문자 앞에 보이지 않는 공백 같은게 있을 수 있는데 예를 들면 다음과 같다.
"윤아;18;96; \n"
이러한 경우에는 마지막의 개행문자를 제거하여 윤아;18;96; 으로 변형하여도 strtok 함수는 다음과 같이 4개의 토큰을 뽑아낼 수 있다.
{"윤아","18","96"," "} // 사실은 윤아\018\096\0 \0
마찬가지로 윤아는 과목2의 시험을 치르고  점의 성적을 받은 것으로 해석될 소지가 있다. 이를 방지하기 위해서 가장 마지막의 데이터 구분자를 삭제하는 것이 좋다. 따라서 strtok을 사용하기 전에 문자열은 다음과 같은 형태가 된다.
"윤아;18;96"
즉, 마지막에 데이터 구분자가 있다면 이를 NULL 문자로 치환해주는 것이 좋으며, 마지막 구분자가 없는 경우, 보이지 않는 공백문자나 개행문자가 숨어 있을 수 있으니 이를 처리해주어야 한다.
#include 문의 <> 와 ""의 차이
컴파일 할 때, gcc -I/usr/include 와 같은 옵션으로 헤더가 있는 디렉토리를 컴파일러로 전달한다. 또는 -I 옵션 없이 gcc 명령을 사용하면 시스템에서 기본으로 지정된 헤더 디렉토리가 자동으로 전달된다. 이 때, <> 는 전달된 디렉토리 안에서 헤더파일을 찾는다. 반면 """"안에 기술된 경로에서 헤더파일을 찾고 없다면 -I 옵션으로 전달된 헤더 디렉토리나 시스템 기본 헤더 디렉토리에서 같은 이름의 헤더 파일을 찾는다.
#include <stdio.h> // -I 옵션으로 전달된 디렉토리나 시스템 디렉토리에서 stdio.h 파일을 찾아서 사용함.
#include "stdio.h" // (상대 경로) 컴파일러를 실행하는 현재 경로에서 stdio.h 파일을 찾아서 사용하고 파일이 존재하지 않는다면 -I 옵션으로 전달된 디렉토리나 시스템 디렉토리에서 stdio.h 파일을 찾아서 사용함.
#include "/some/path/stdio.h" // (절대 경로) /some/path/ 디렉토리에서 stdio.h 파일을 찾아서 사용하고 파일이 존재하지 않는다면 -I 옵션으로 전달된 디렉토리나 시스템 디렉토리에서 stdio.h 파일을 찾아서 사용함.
sys/poll.hpoll()함수는 이벤트를 기다리는 함수이다.
http://forum.falinux.com/zbxe/index.php?document_srl=405838&mid=network_programming
전처리(preprocessing) 매크로
전처리 지시어(preprocessing directive)
C언어 문장과 구분하기 위하여 선두에 반드시 # 기호를 붙인다. #기호가 붙은 전처리 지시어는 컴파일러에 의해 처리되지 않고, 전처리기에 의해서 처리된다.
전처리 지시어의 종류
- #include : 파일 처리를 위한 전처리문
- #define, #undefine : 형태정의를 위한 전처리문
- #if, #else, #elif, #endif, #ifdef, #ifndef : 조건 처리를 위한 전처리문
- #error : 에러처리를 위한 전처리문
- #line : 디버깅을 위한 전처리문
- #pragma : 컴파일 옵션 처리를 위한 전처리문
#include
C언어로 작성된 특정파일을 소스에 포함시키기 위해 사용한다.
#include <stdio.h>
#include "custom_header.h"
#include "../another_custom_header.h"
#include "/home/hodol/hello/world/the_other_custom_header.h"
#define
상수값을 지정하기 위한 예약어로 매크로라고 부르며 사용문법은 #define [매크로명] [실제값] 이다. 단순히 매크로명을 실제 값을 치환해주는 역할을 한다. 함수/변수명과 구분하기 위해 주로 대문자로 매크로 명을 쓰는 것이 관례이다.
단순히 매크로명을 대응 문자열 또는 상수로 치환
#define ABC "DEF"
printf(ABC);
매크로 함수를 만들어 사용
#deinfe PAYCALC(a, b) ((a) * (b) * 4000)
printf("My Pay? : %d\n",PAYCALC(8,30));
#undef
#define으로 이미 정의된 매크로를 무화화한다.
#define ADD(a,b)(a+b)
...
#undef ADD(a,b)
#undef 으로 무효화된 메크로를 재사용을 할경우에는 undefined symbol 에러 처리 된다.
#if, #else, #elif, #endif, #ifdef, #ifndef
특정한 조건이 성립될 때나 또는 성립되지 않을 때 지정된 범위 내의 문장을 컴파일하거나 또는 그냥 무시하고 컴파일하지 않는 것을 말한다. C에서는 0이 아닌 정수는 모두 true 값으로 간주한다.
#if 상수수식1
문장1
#elif 상수수식2
문장2
#else
문장3
#endif
#ifdef, #ifndef
매크로(#define) 이 정의 되어 있는지를 판단한다. 사용문법은 #ifdef [매크로명],#ifndef[매크로명] 이다. #ifdef 는 if define 의 약자로 매크로가 정의되있을경우 참(1)으로 정의가 되어있지 않을 경우 거짓(0)으로 평가한다. #ifndef 는 if not define 의 약자로 매크로가 정의되어있을경우 거짓(0)으로 정의되어 있지 않을 경우 참(1)으로 평가한다.
예1)
#define TEST1(a,b) (a+b)
#ifdef TEST1
printf("Not Define");
#else
printf("Define");
#endif
#ifndef 헤더명_H__ ~ #endif
헤더파일이 겹치는 것을 막기 위한 일종의 매크로이다. 예를 들어 두 파일이 하나의 헤더 파일을 include하며, 나중에 이 두파일을 링크시켜 최종 실행파일을 만드는 경우 해당 헤더파일이 두 파일에 각각 포함되어 중복된다.
file1.c
#include <stdio.h>
...
file2.c
#include <stdio.h>
...
앞에 이미 stdio.h를 include했는데 밑에 또 한다면 문제가 된다. 그래서 stdio.h에는
#ifndef STDIO_H__
#define STDIO_H__
...
stdio.h의 본문
...
#endif
가 선언되어 있다. 만약 STDIO_H__가 선언되어 있지 않다면 선언을 하고 stdio.h을 컴파일 하며, 그 뒤 다음 include <stdio.h>/q>에서는 이미 STDIO_H__가 선언되었기 때문에 전처리기 쪽에서 무시해버린다. 그래서 컴파일러는 stdio.h를 한번만 컴파일한다.
#defined
define이 여러 개 되어 있는지를 검사할 때 쓴다. 이것은 여러 개를 동시에 검사 할 수 있다.
#if #defined A || #defined B
#ifdef와 #if defined의 차이
#ifdef는 한번에 여러 개를 사용할 수 없다. 여러 개가 정의되어 있는지를 테스트 하기 위해서는
#if defined(A) || defined(B)
#if C || D
처럼 사용한다.
#error
소스 라인에 직접 에러 메세지를 출력한다. 전처리기가 #error 문을 만나면 그 즉시 컴파일을 중단하고 다음과 같은 에러 메시지를 출력한다.
#ifdef __A__
#error filename.c 90 : This program must be compiled with A..
#endif
함수 포인터 선언 방법
함수 포인터 선언에는 다음 두가지 방법이 있다.
void (*func)(int);
typedef void (*func)(int x);
typedef를 쓰는 경우, typedef로 새로운 타입을 정의했으니 해당 함수포인터를 인자로 받는 함수의 인자목록 작성시,
void otherFunc(int a, int b, void (*func)(int));
로 선언해야 되는 걸
void otherFunc(int a, int b, func fp);
같이 간결하게 표현이 가능하다.
extern void( * signal( int, void( * )(int))) (int);
1 2 3 4 5 6 7 8
: signal() 함수(3)는 인자로 정수(4)와 정수 하나(7)를 인자로 가지며 반환값이 없는(5) 함수의 포인터(6)를 가지며, 정수 하나(8)를 인자로 가지며 반환값이 없는(1) 함수의 포인터(2)를 반환한다.
typedef void ( * SignalHandler) (int signum);
1 2 3 4
extern SignalHandler signal( int signum, SignalHandler handler);
5 6 7 8
:SignalHandler(3)는 정수 하나(4)를 인자로 가지며 반환값이 없는(1) 함수를 가리키는 포인터(2)이다. signal()함수(6)는 인자로 정수(7)와 SignalHandler형태의 함수 포인터(8)을 가지며 SignalHandler 형태의 함수 포인터(6)를 반환한다.
지역변수와 스택
지역변수는 스택에 쌓인다. 스택은 후입선출의 데이터 반입구조를 가지고 있다. 지역변수의 "지역"은 코드상에서 중괄호( { ... } )로 묶어진 부분이다. 하나의 지역 안에 다른 지역이 있는 경우, 지역의 시작 ( { )은 두번째 지역이 늦게 시작하며, 지역의 끝은 ( } ) 두번째 지역이 빨리 끝난다. 즉, 늦게 시작한 것이 먼저 끝나는 구조는 늦게 들어온 데이터가 먼저 나가는 스택의 구조와 유사하다. 지역의 시작부에 스택을 올려 저장 공간을 확보하고 지역의 종료부에서 스택을 올린만큼 내려서 확보했던 저장 공간을 지우면 된다. 이 스택이라는 메모리 공간은 비교적 작게 할당되기 때문에 아껴쓸 필요가 있다.
Call by value and Call by reference
스택이 비교적 제한적인 메모리 공간이기 때문에 구조체라던가 배열을 할당하는 경우, 구조체 자체를 스택에 넣어 쓰느냐(Call by value), 구조체는 자유공간에 할당하고 구조체를 가리키는 포인터를 스택에 넣어 쓰느냐(Call by reference)의 문제이다. 예를 들면
struct gujoche { int x; char * y; }
int func1(struct gujoche gujo){.....}
int func2(struct gujoche *gujo){.....}
보통 함수의 인자는 스택에 할당되는데, 두 함수를 비교하면 func1의 경우, gujo.xgujo.y가 스택에 올라가 2개의 공간을 차지하지만 func2의 경우, gujo->xgujo->y는 자유공간에 할당되어 있고, 이 gujo를 가리키는 포인터 가 스택에 올라가서 1개의 공간을 차지한다. 따라서 Call by reference를 사용하는 것이 좋다.
메모리 할당 함수 벤치마크
http://locklessinc.com/benchmarks_allocator.shtml
오파크 타입(Opaque Type)
C++에서의 정보 은닉과 비슷한 개념이다. 라이브러리를 컴파일할 때 사용하는 헤더랑 라이브러리를 배포할 때의 헤더를 다르게 작성하여 컴파일할 때 구조체의 내부 구조를 은닉한다. 컴파일 할 때는 typedef struct someStruct *opaqueType로, 배포할 때는 typedef void *opaqueType로 작성하여 구조체 someStruct의 내부 정보를 은닉한다.
_t로 끝나는 자료형
ssize_t, size_t와 같은 자료형이 있다. 고전적(Primitive) 자료형이라 불리는 이들은 sys/types.h 헤더에서 각각 signed int, unsinged inttypedef 정의되어 있다. 왜 귀찮게 재정의를 해서 사용하는걸까?
오래 전 컴퓨터는 16비트였고, 90년대에서 2000년대 초반에는 32비트, 그리고 2016년 현재에는 64비트 OS가 늘어나는 추세이다. 이 16비트, 32비트, 64비트라고 하는 것은 시스템의 자료형의 메모리 기본단위이다. 32비트 컴퓨터를 위해 작성된 프로그램을 64비트에서 작동시키면 오류를 일으킨다. 따라서 프로그램을 새로운 64비트 컴퓨터에서 사용하기 위해서는 프로그램의 소스 코드를 수정해야한다.
그러나 예를 들어, 현재 4바이트로 표현되는 unsigned intsize_t로 정의해놓고 쓰다가 나중에 unsigned int가 더 이상 4바이트가 아니게 되었을 때, 적절한 4바이트짜리 자료형을 size_t로 다시 정의만 하여 컴파일하면 된다. 즉, 소스 코드를 수정할 필요없이 컴파일만 다시 해주면 된다.
후기
나: C 언어로 OOO한 XXX를 만들어 봤어요.
회사 선배: 지금 그 말은 라이터가 있는데 "부싯돌로 불을 붙였어요."라고 하는 거랑 같은 거예요.
나: !?