예전에는, 특정 run-time library는 C 언어의 표준의 한 부분이 아니었습니다. ANSI/ISO Standard C의 출현으로 대부분의 전통적인 (traditional) run-time library는 (Chapter 12의 stdio 함수들을 포함) 표준이 되었습니다.
특별히 중요한 라이브러리 함수들은 각각 다른 section에 설명되었습니다; 메모리
할당에 관련된 malloc과 free에 관한 것은 7 장에
설명되었으며, <stdio.h>
에 나온 “standard I/O” 함수들은
Chapter 12에 설명되었습니다.
이 chapter는 다음과 같이 나누어집니다:
마지막 몇 개의 질문들은 ( 13.25부터 13.28까지) link시 문제가 되는 (예를 들면, “undefined external” 에러) 것을 다루었습니다.
long이나 실수도 sprintf()를 써서 바꿀 수 있습니다 (각각 %ld와 %f를 쓰면 됨); 즉, sprintf를 atol이나 atof의 반대 역할을 하는 함수라고 생각할 수 있습니다. 게다가, 어떤 식으로 출력할 것인지도 제어할 수 있습니다. (그렇기 때문에, C 언어는 itoa 식의 함수보다, sprintf를 제공하는 것입니다.)
꼭 itoa를 직접 만들어야 한다면, 다음과 같은 사항들을 고려해야 합니다:
마찬가지로, size_t
타입의 값을 쉽게 출력할 수 있도록
printf, scanf 형태의
함수에 z modifier를 추가했습니다. 정수 출력에 쓰이는 conversion인,
d, i, o, u, x, X에 z를 붙여서
size_t
를 쉽게 출력할 수 있습니다. 예를 들면:
size_t sz; ... printf("%zu\n", sz);
\0
을 써 주지
않는 것일까요?
\0
으로
끝나지 않는 “문자열”13.1도 처리할 수
있게 하는 것이 그 목적이었습니다.
따라서, 다른 목적으로 strncpy()를 쓸 때에는 좀 번거로운 작업이
필요합니다; (strncpy()의 한 가지 단점은 여분의 공간에 0을 계속
기록한다는 것입니다.) 복사한 문자열이 저장된 버퍼의 끝에
수동으로 '\0'
을 채워주어야 할 필요가 있습니다. 그래서 그리 자주
쓰이는 함수가 아닙니다.
그래서, strncpy() 대신 strncat을
쓰기도 합니다: 대상 버퍼가 비어있는 경우, strncat은 여러분이
strncpy에서 기대한 작업을 수행해 줍니다:
*dest = '\0'; strncat(dest, source, n);위 코드는 최대 n개의 문자를 복사하며, 항상 마지막에
또 다른 방법은 다음과 같습니다:
sprintf (dest, "\%.*s", n, source);정확하게는 위 코드는 n이 509 이하일때에만 동작합니다.
문자열이 아닌 어떤 크기의 바이트를 복사하고자 한다면 strncpy() 대신 memcpy()를 쓰는 것이 더 효과적입니다.
원본 문자열의 POS번째에서 시작해서 길이가 LEN인 부분 문자열을 꺼내기 위해서 다음과 같이 할 수 있습니다:
char dest[LEN + 1]; strncpy(dest, &source[POS], LEN); dest[LEN] = '\0'; /* ensure \0 termination */또, 질문 13.2에서 다룬 것처럼 다음과 같이 할 수 있습니다:
char dest[LEN + 1] = ""; strncat(dest, &source[POS], LEN);다음과 같이 포인터 형태로 할 수도 있습니다:
strncat(dest, source + POS, LEN);(정의에 의해, &source[POS]는 source + POS와 완벽히 같은 표현입니다; Chapter 6 참고)
<ctype.h>
에 정의되어 있는 매크로인
toupper와
tolower를 써서 이 기능을 수행하는 함수를 만들면 됩니다;
질문
13.5를 참고 바랍니다. (매우 간단하지만, 만들 함수가
전달받은 문자열을 고칠 것이냐, 아니면 따로 공간을 할당하고 변경된
문자열을 그 곳에 저장할 것이냐는 미리 생각해 두어야 합니다;
질문
7.5를 참고 바랍니다.)
(다국적(multinatinoal) 문자 셋을 쓸 때에는 문자 또는 문자열을 대문자 또는 소문자로 바꾸는 것은 더 복잡합니다.)
그러나, C 표준은 이러한 함수들이 어떤 값이 전달되더라도 안전하게 동작한다고 말하고 있습니다. 즉, 변경이 필요없는 문자들은 변경되지 않습니다.
#include <string.h> char string[] = "this is a test"; /* not char *; see Q16.6 */ char *p; for (p = strtok(string, " \t\n"); p != NULL; p = strtok(NULL, " \t\n")) printf("\"%s\"\n", p);
또, 아래는 argv를 한꺼번에 만들어 주는 함수입니다:
#include <ctype.h> int makeargv(char *string, char *argv[], int argvsize) { char *p = string; int i; int argc = 0; for (i = 0; i < argvsize; i++) { /* skip leading whitespace */ while (isspace(*p)) p++; if (*p != '\0') argv[argc++] = p; else { argv[argc] = 0; break; } /* scan over arg */ while (*p != '\0' && !isspace(*p)) p++; /* terminate arg: */ if (*p != '\0' && i < argvsize - 1) *p++ = '\0'; } return argc; }makeargv는 다음과 같이 호출합니다:
char *av[10]; int i, ac = makeargv(string, av, 10); for (i = 0; i < ac; i++) printf("\"%s\"\n", av[i]);만약 구분(separator) 문자가 중요하다면 -- 즉, 두 개의 탭이 동시에 나올 때, 한 필드가 생략되었다는 것을 알리고 싶으면 -- strchr 함수를 쓰는 것이 더 편할 수 있습니다:
#include <string.h> char *p = string; while (1) { /* break in middle */ char *p2 = strchr(p, '\t'); if (p2 != NULL) *p2 = '\0'; printf("\"%s\"\n", p); if (p2 == NULL) break; p = p2 + 1; }
여기에 나온 모든 코드는 원본 문자열에, 각 필드가 끝날 때마다
0을 넣어서 각 필드를 구분할 수 있게 만듭니다. 따라서 원본
문자열이 나중에 다시 필요하다면, 이 곳에 있는 코드를 실행하기 전에
먼저 복사해 두어야 합니다.
정규식 매칭에 사용되는 패키지는 많습니다. 그리고 대부분의 패키지들은
두 가지 함수를 사용합니다: 하나는 정규식을 “컴파일(compile)”하기
위한 것이고, 다른 하나는 이 “컴파일된” 정규식과 문자열을
비교해주는(execute) 함수입니다.
먼저 헤더 파일 <regex.h>
나 <regexp.h>
가 있는지,
그리고 regcmp/regex, regcomp/regexec,
re_comp/re_exec
함수가 존재하는 지 (이 함수들은 대개
각각의 독립적인 라이브러리 형태로 제공됩니다.) 체크해보시기 바랍니다.
인기있고, 공개인 Henry Spencer씨의 regexp 패키지를
ftp.cs.toronto.edu에서 pub/regexp.shar.Z로 얻을 수
있습니다. GNU 프로젝트에서는 rx라고 하는 패키지를 제공합니다.
덧붙여 질문
18.16도 참고하시기 바랍니다.
Filename wildcard matching은 (때때로 “globbing”이라고 함) 각각의 시스템에 따라 다르게 이루어집니다. UNIX에서는 와일드 카드를 shell이 처리해 주므로, 각각의 프로그램들은 이 와일드 카드에 대해 전혀 고려하지 않아도 됩니다. 그러나 MS-DOS에서는 shell이라 할 수 있는 command interpreter가 이를 처리해 주지 않으므로, (컴파일러가 제공하는) 와일드 카드를 처리해주는 특별한 오브젝트 파일과 함께 링크해야 합니다. (이 오브젝트 파일은 agrv를, 와일드 카드 처리한 다음의 상태로 만들어 줍니다) 그리고 (MS-DOS와 VMS를 포함한) 대부분의 시스템에서는 와일드카드로 파일을 리스팅해주거나, 파일을 open하는 시스템 서비스들을 제공합니다. 먼저 컴파일러/라이브러리의 문서를 참고하기 바랍니다. 덧붙여 질문 19.20, 20.3도 참고하시기 바랍니다.
아래는 Arjan Kenter씨가 제공한 간단한 형태의 wildcard를 처리하는 함수입니다:
int match(char *pat, char *str) { switch (*pat) { case '\0': return !*str; case '*': return match(pat + 1, str) || *str && match(pat, str + 1); case '?': return *str && match(pat + 1, str + 1); default: return *pat == *src && match(pat + 1, str + 1); }이 함수를
match("a*b.c", "aplomb.c")
로 부르면,
1을 리턴합니다.
/* compare strings via pointers */ int pstrcmp(const void *p1, const void *p2) { return strcmp(*(char * const *)p1, *(char * const *)p2); }
비교 함수의 인자는 “범용 포인터”인 const void *
이어야
합니다. 그리고 함수 내부에서 이를 원하는 형태로 (예를 들면 문자를
가리키는 포인터) 바꾸어 씁니다. 즉 이 타입의 인자는 함수 내부에서
원래의 타입인 char **로 바뀌어지고, 다시 실제로 가리키는 타입인
char *를 얻어서 strcmp 함수에 전달됩니다. (ANSI 이전
컴파일러에서 쓸 때에는 void * 대신에 char *를 쓰고,
모든 const를 빼면 됩니다.)
실제 qsort는 다음과 같이 부릅니다:
#include <stdlib.h> char *strings[NSTRINGS]; int nstrings; /* nstrings cells of strings[] are to be sorted */ qsort(strings, nstrings, sizeof(char *), pstrcmp);
([K&R2] § 5.11 pp. 119-20에 나온 것을 혼동하면 안됩니다. 그것은 표준 함수인 qsort와는 관계없으며, char *와 void *가 서로 같다는 불필요한 암묵적인 가정을 하고 쓴 것입니다.)
qsort 비교 함수에 대한 더 자세한 정보는 -- 어떻게 선언되고 어떻게 불리워지는가에 대한 -- 질문 13.9를 보기 바랍니다.
struct mystruct { int year, month, day; }이 때, 비교 함수는 다음과 같이 만들어야 합니다:
int mystructcmp(const void *p1, const void *p2) { const struct mystruct *sp1 = p1; const struct mystruct *sp2 = p2; if (sp1->year < sp2->year) return -1; else if (sp1->year > sp2->year) return 1; else if (sp1->month < sp2->month) return -1; else if (sp1->month > sp2->month) return 1; else if (sp1->day < sp2->day) return -1; else if (sp1->day > sp2->day) return 1; else return 0; }(Generic 포인터를 struct mystruct 포인터로 변경하는 것은, 초기화할 때인 sp1 = p1, sp2 = p2에서 이루어졌습니다; p1과 p2가 void 포인터이기 때문에, 컴파일러는 이 conversion을 자동으로 해 줍니다. ANSI 이전의 컴파일러에서 쓰려면 강제로 캐스팅하고, char * 포인터를 쓰면 됩니다. 질문 7.7 참고)
이런 형태의 mystructcmp를 쓰기 위해, 다음과 같이 qsort를 부릅니다:
#include <stdlib.h> struct mystruct dates[NDATES]; int ndates; /* ndates cells of dates[] are to be sorted */ qsort(dates, ndates, sizeof(struct mystruct), mystructcmp);
만약, structure를 가리키는 포인터를 정렬하고 싶다면, 질문 13.8에 나온 것처럼 indirection이 필요합니다. 이 때 비교 함수는 다음과 같이 시작합니다:
int myptrstructcmp(const void *p1, const void *p2) { struct mystruct *p1 = *(struct mystruct * const *)p1; struct mystruct *p2 = *(struct mystruct * const *)p2;그리고, 다음과 같이 부릅니다:
struct mystruct *dateptrs[NDATES]; qsort(dateptrs, ndates, sizeof(struct mystruct *), myptrstructcmp);qsort 함수에서 쓰이는 비교 함수에 왜 이런 포인터 변환이 필요한지 이해하기 위해서 (그리고 왜 qsort를 부를때 함수 포인터를 캐스팅하는 것이 불가능한지) qsort가 어떤 식으로 동작하는지 알 필요가 있습니다: qsort는 정렬할 대상이 어떤 타입인지, 어떤 식으로 표현되어 있는지 전혀 알지 못합니다. qsort는 단지, 전달된 조그만 메모리 블럭(little chunks of memory)들을 섞어 줄 뿐입니다. (이 메모리 블럭에 대해 알고 있는 것은, qsort의 세번째 인자로 받은 블럭의 크기입니다.) 두 개의 블럭을 교환(swap)해야 할 경우, qsort는 여러분이 만든 비교 함수를 부릅니다. (실제로 교환하는 것은 memcpy가 하는 것과 같은 방식으로 이루어집니다.)
qsort는 어떤 타입인지 알지 못하는, 일반적인 타입에 대한 정렬을 하므로, 정렬 대상을 가리키기 위해, generic 포인터를 (void *) 씁니다. qsort가 비교 함수를 부를때, 여러분이 만든 비교 함수는 void *를 인자로 받도록 만들어져 있어야 하며, 실제 정렬 대상에 대해 어떤 작업을 (예를 들어 실제 비교를 하기 위해) 수행하기 위해서, 인자로 받은 포인터를 원하는 타입으로 다시 변경해야 합니다. 어떤 시스템에서는 void 포인터가 structure 포인터와 서로 다른 표현 방식을 씁니다; 예를 들어, 실제 포인터의 길이가 다를 수 있으며, 내부 표현 방식이 다를 수 있습니다. (그렇기 때문에 캐스팅이 필요합니다.)
어떤 Structure에 대한 배열을 정렬한다고 가정해봅시다. 그리고 비교 함수가 이 structure에 대한 포인터를 받는다고 하면 다음과 같습니다:
int mywrongstructcmp(struct mystruct *, struct mystruct *);만약 다음과 같이 qsort를 부르게 되면:
qsort(dates, ndates, sizeof(struct mystruct), (int (*)(const void *, const void*)mywrongstructcmp); /* WRONG */이 것은, 컴파일러가 “qsort에 전달된 비교 함수가 제대로 동작하지 않을 수 있다”고 경고하는 것만을 없애 줄 수 있을 뿐입니다. 여기서 사용한 캐스팅은 실제 qsort가 여러분의 비교 함수를 부를 때에는 이미 알 수 없는 것입니다: 즉, qsort는 const void * 타입의 인자를 만들 것이고, 여러분이 만든 비교 함수는 이 인자를 반드시 받을 수 있어야 합니다. 어떠한 메카니즘도, qsort가 mywrongstructcmp를 부를 때, void 포인터를 struct mystruct 포인터로 바꿔 줄 수 없습니다.
따라서, 일반적으로 위와 같이 단순히 컴파일러 경고를 없애기 위한 캐스팅은 매우 나쁜 방법입니다. 컴파일러 경고는 보통 여러분에게 어떤 사항을 알려 줄려고 나온 것이기 때문에, 여러분이 어떤 식으로 동작하는지 잘 모르고 이 경고를 무시한다면, 그 결과 매우 위험한 상태가 될 수 있습니다. 덧붙여 질문 4.9도 참고하시기 바랍니다..
#include <stdio.h> #include <time.h> int main() { time_t now; time(&now); printf("It's %.24s.\n", ctime(&now)); return 0; }
localtime과 strftime은 다음과 같이 씁니다:
struct tm *tmp = localtime(&now); char fmtbuf[30]; printf("It's %d:%02d:%02d\n", tmp->tm_hour, tmp->tm_min, tmp->tm_sec); strftime(fmtbuf, sizeof fmtbuf, "%A, %B %d, %Y", tmp); printf("on %s\n", fmtbuf);
(이 함수들이 주어진 값을 고치지 않는데도
time_t
변수에 대한 포인터를
인자로 받는 것에 주의하기 바랍니다.13.3
time_t
를
struct tm
형태로 바꾸어 줍니다. 그리고 ctime()은
time_t
를 문자열로 바꾸어 줍니다. 거꾸로 변환하고 싶으면
어떻게 해야 할까요? 즉, 문자열이나 struct tm
을
time_t
타입으로 변경하고 싶습니다.
time_t
로 바꾸어 주는
mktime() 함수를 제공합니다.
문자열을 time_t
로 바꾸는 것은 조금 힘듭니다. 왜냐하면,
각 나라 혹은 지역별로 사용하는 날짜와 시간 형식이 각각 다르기
때문입니다. 어떤 시스템은 strptime()이라는 함수를 제공하며,
그 기능은 strftime()의 반대입니다. 또 (RCS 패키지와 함께
제공되는) partime()이라는 함수와 (최신의 C news 배포판에서
제공하는) getdate()라는 함수도 자주 쓰입니다.
질문
18.16을 보기 바랍니다.
struct tm
타입으로 입력받아 이것을 정규화시켜 줍니다.
즉, struct tm의 변수에 기준 날짜 값을 넣고,
tm_mday
에서 원하는 날짜만큼 더하거나 뺀 다음, mktime을
부르면 normalize된 년, 월, 일을 얻을 수 있습니다.
(추가적으로 이 값을 time_t
타입으로 변경시켜 줍니다.)
difftime()은 두 개의 time_t
값을 입력받아 초 단위로
비교해 줍니다; 이 때 필요한 time_t
값은 mktime()을 써서
얻습니다.
그러나 이 해결책들은 주어진 날짜 값이 time_t
범위 안에
있을 때만 동작합니다.
tm_days
필드는 int이기 때문에 일(day) 수가
int 값을 넘는 값일 경우 (예를 들어 32,736 이상), 오버플로우가
발생할 수 있습니다.
또한 daylight saving time(써머 타임)을 고려하면
하루는 24 시간이 아닐 수도 있습니다 (따라서 86400 seconds/day로 나누면
해결될 것이라는 생각을 하면 안됩니다).
다음은 October 24, 1994에서 90일을 빼는 코드입니다:
#include <stdio.h> #include <time.h> tm1.tm_mon = 10 - 1; tm1.tm_mday = 24; tm1.tm_year = 1994 - 1900; tm1.tm_hour = tm1.tm_min = tm1_tm_sec = 0; tm1.tm_isdst = -1; tm1.tm_mday -= 90; if (mktime(&tm1) == -1) fprintf(stderr, "mktime failed.\n"); else printf("%d/%d/%d\n", tm1.tm_mon + 1, tm1.tm_mday, tm1.tm_year + 1900);(
tm_isdst
를 -1로 설정하거나 tm_hour
를
12로 설정하면 daylight saving time 문제로부터 보호받는데
도움을 줍니다.)
아래 코드는 2000년 2월 28일과 2000년 3월 1일 사이의 날짜 차이를 구하는 코드입니다:
struct tm tm1, tm2; time_t t1, t2; tm1.tm_mon = 2 - 1; tm1.tm_mday = 28; tm1.tm_year = 2000 - 1900; tm1.tm_hour = tm1.tm_min = tm1.tm_sec = 0; tm1.tm_isdst = -1; tm2.tm_mon = 3 - 1; tm2.tm_mday = 1; tm2.tm_year = 2000 - 1900; tm2.tm_hour = tm2.tm_min = tm2.tm_sec = 0; t1 = mktime(&tm1); t2 = mktime(&tm2); if (t1 == -1 || t2 == -1) fprintf(stderr, "mktime failed\n"); else { long d = (difftime(t2, t1) + 86400L/2) / 86400L; printf("%ld\n", d); }(위에서 추가적으로 쓴 86400L / 2는 계산 결과를 원하는 근사값으로 만들어 줍니다; 질문 14.6 참고)
“Julian day number”를 (또는 4013 BC13.4 1월 1일) 사용하는 방법도 있습니다. 아래는 Julian day로 바꾸는 함수의 선언입니다:
/* returns Julian for month, day, year */ long ToJul(int month, int day, int year); /* returns month, day, year for jul */ void FromJul(long jul, int *monthp, int *dayp, int *yearp);이 때, n일(day)을 더하는 것은 다음과 같이 합니다:
int n = 90; int month, day, year; FromJul(ToJul(10, 24, 1994) + n, &month, &day, &year);또, 두 날짜의 차이(일 수 단위로)는 다음과 같이 구합니다:
ToJul(3, 1, 2000) - ToJul(2, 28, 2000)Julian day를 다루는 함수들은 Snippets 콜렉션에 (질문 18.15c 참고) 있습니다. 그리고 이 콜렉션은 Simtel/Oakland 아카이브(archive)에서 (파일 JULCAL10.ZIP, 질문 18.16 참고) 얻을 수 있습니다. 아래의 “Date conversions” 기사도 참고하기 바랍니다.
struct tm의 tm_year
필드는 1900년대에서 시작한 년도를
가지고 있습니다. 따라서 2000년일 경우에 이 값은 100이 됩니다.
tm_year
를 제대로 사용하는 코드라면 (변환할 때, 1900을 더하거나
빼는) 전혀 문제될 것이 없습니다. 그러나 이 값을 두 자리 숫자로
바로 사용한다거나 다음과 갈은 코드를 썼다면:
tm.tm_year = yyyy % 100; /* WRONG */또는, 다음과 같이 했다면:
printf("19%d", tm.tm_year); /* WRONG */2000년 문제가 발생합니다. 덧붙여 질문 20.32도 참고하시기 바랍니다.
직접 난수 발생기를 만들려고 한다면, 읽어야 할 책들이 꽤 많습니다; 이 글 뒷부분에 있는 Reference를 보시기 바랍니다. 또한 인터넷 상에 많은 패키지가 이미 올라와 있습니다; r250, RANLIB, FSULTRA로 검색해 보기 바랍니다. (질문 18.16도 참고.)
아래는 Park씨와 Miller씨에 의해 제안된, “minimal standard” generator입니다.
#define a 16807 #define m 2147483647 #define q (m / a) #define r (m % a) static long int seed = -1; long int PMrand() { long int hi = seed / q; long int lo = seed % q; long int test = a * lo - r * hi; if (test > 0) seed = test; else seed = test + m; return seed; }(The “minimal standard” is adequately good; it is something “against which all others should be judged” and is recommended for use “unless one has access to a random number generator known to be better.”)
이 코드는 a = 16807이고, m = 2147483647(이 값은
입니다)이고, c = 0인, 다음 generator를 구현합니다:
double PMrand(void);마지막 줄을 다음과 같이 고칩니다:
return (double)seed / m;통계적으로 좀 더 좋은 결과를 얻으려면, (Park씨와 Miller씨의 추천에 따르면) a = 48271을 쓰기 바랍니다.
rand() % N /* POOR */왜냐하면, 대부분의 난수 발생기에서 하위(low-order) 비트들은 그리 랜덤하지 않기 때문입니다. (질문 13.18을 보기 바랍니다.) 따라서, 다음과 같이 하는 것이 좋습니다:
(int)((double)rand() / ((double)RAND_MAX + 1) * N)실수를 쓰기 싫다면, 다음과 같이 해도 좋습니다:
rand() / (RAND_MAX / N + 1)두 방법 모두 (ANSI 표준에 의해
<stdlib.h>
에
정의되어 있는) RAND_MAX
를 사용하고, N이
RAND_MAX
보다 아주 작은 값이라는 것을 가정한 방법입니다.
만약 N이 RAND_MAX
에 가깝고, random number generator의
범위가 N의 배수가 아니라면 (즉,
RAND_MAX + 1 % N != 0
인
경우) 위 모든 방법이 나쁜 방법이 됩니다: 어떤 범위의 수치들이 다른
범위보다 훨씬 더 많이 나타나게 됩니다. (실수를 쓴다고 해결될 문제가
아닙니다. 문제는 rand 함수가 서로 다른
RAND_MAX + 1
가지의 값을 리턴하고, 이 것을 N개의 묶음으로
공평하게 나눌 수 없기 때문에 발생하는 것입니다.)
만약 이것이 문제가 된다면, rand를 여러 번 불러서, 특정 값들을
무시하면 됩니다:
unsigned int x = (RAND_MAX + 1u) / N; unsigned int y = x * N; unsigned int r; do { r = rand(); } while (r >= y); return r / x;또, [M, N]의 범위를 가지는 random number를 구하고 싶다면, 위 결과를 shift해서 얻을 수 있습니다:
M + rand() / (RAND_MAX / (N - M + 1) + 1)(어쨋든,
RAND_MAX
는 rand()가 리턴할 수 있는 최대 수치를
나타내는 상수라는 것을 알아 두시기 바랍니다. RAND_MAX
에
여러분이 다른 수치를 대입할 수는 없으며, rand()가 다른 범위의
수치를 리턴하도록 해 주는 방법은 존재하지 않습니다.)
만약에 여러분이 (질문 13.15의 마지막 PMrand 함수 또는 질문 13.21의 drand48 함수처럼) 0과 1 사이의 범위를 가지는 난수 발생기를 가지고 있다면, 그리고 0과 N-1 사이의 범위를 가지는 정수 난수를 만들고 싶다면, 단순히 그 난수 발생기의 수치에 N을 곱하면 됩니다:
(int)(drand48() * N)
#include <stdlib.h> #include <time.h> srand((unsigned int)time((time_t *)NULL));
(일반적으로 프로그램 내에서는 srand() 함수를 한번만 불러주어도 충분합니다. rand()를 부를 때마다, srand()를 부른다면, random number를 얻을 수 없습니다.)
rand() % 2
를 썼는데, 계속 0, 1, 0, 1, 0 만을 반복합니다.
int a[10], i, nvalues = 10; for (i = 0; i < nvalues; i++) a[i] = i + 1; for (i = 0; i < nvalues - 1; i++) { int c = randrange(nvalues - i); int t = a[i]; a[i] = a[i + c]; a[i + c] = t; /* swap */ }여기에서 randrange(N)은 rand() / (RAND_MAX / (N) + 1) 또는 질문 13.16에 나온 코드를 씁니다.
#include <stdlib.h> #include <math.h> #define NSUM 25 double gaussrand() { double x = 0; int i; for (i = 0; i < NSUM; i++) x += (double)rand() / RAND_MAX; x -= NSUM / 2.0; x /= sqrt(NSUM / 12.0); return x; }(Don't overlook the sqrt(NSUM / 12.) correction, although it's easy to do so accidentally, especially when NSUM is 12.)
#include <stdlib.h> #include <math.h> #define PI 3.141592654 double gaussrand() { static double U, V; static int phase = 0; double z; if (phase == 0) { U = (rand() + 1.) / (RAND_MAX + 2.); V = rand() / (RAND_MAX + 1.); Z = sqrt(-2 * log(U)) * sin(2 * PI * V); } else Z = sqrt(-2 * log(U)) * cos(2 * PI * V); phase = 1 - phase; return Z; }
#include <stdlib.h> #include <math.h> double gaussrand() { static double V1, V2, S; static int phase = 0; double X; if (phase == 0) { do { double U1 = (double)rand() / RAND_MAX; double U2 = (double)rand() / RAND_MAX; V1 = 2 * U1 - 1; V2 = 2 * U2 - 1; S = V1 * V1 + V2 * V2; } while (S >= 1 || S == 0); X = V1 * sqrt(-2 * log(S) / S); } else X = V2 * sqrt(-2 * log(S) / S); phase = 1 - phase; return X; }
#include <stdlib.h> double drand48(void) { return rand() / (RAND_MAX + 1.); }
좀 더 정확한 값을 (drand48처럼 48 bit의 precision을 갖는) 돌려주는 함수는 다음과 같습니다:
#define PRECISION 2.82e14 /* 2**48, rounded up */ double drand48(void) { double x = 0; double denom = RAND_MAX + 1.; double need; for (need = PRECISION; need > 1; need /= (RAND_MAX + 1.)) { x += rand() / denom; denom *= RAND_MAX + 1.; } return x; }Before using code like this, though, beware that it is numerically suspect, particularly if (as is usually the case) the period of rand is on the order of
RAND_MAX
. (If you have a
longer-period random number generator available, such as BSD
random, definitely use it when simulating drand48.)
index | strchr 함수를 쓰기 바랍니다. |
rindex | strrchr 함수를 쓰기 바랍니다. |
bcopy | 첫 번째 인자와 두 번째 인자를 바꾸어서 memmove 함수를 쓰기 바랍니다. 덧붙여 질문 11.25도 참고하시기 바랍니다. |
bcmp | memcmp 함수를 쓰기 바랍니다. |
bzero | 두 번째 인자를 0으로 해서 memset 함수를 쓰기 바랍니다. |
만약, 오래된 시스템을 쓰고 있고, 위 표의 오른쪽에 있는 함수들을 쓰는 프로그램을 포팅해야 한다면, 왼쪽에 있는 함수로 바꾸거나, 아니면 새 함수들을 만들 수 있습니다. 덧붙여 질문 12.22, 13.21도 참고하시기 바랍니다.
<unp.h>
에서
표준 함수로 (매크로를 써서) 예전 함수를 만들어 두었습니다.
#include
시켰는데도
라이브러리 함수가 정의되어 있지 않다고 에러가 발생합니다.
#include
하는 것만으로는 불충분합니다.)
덧붙여 질문
11.30,
13.26,
14.3도 참고하시기 바랍니다.
예를 들어, 대부분 UNIX 시스템에서 다음과 같이 명령을 실행하면, 보통 동작하지 않습니다:
cc -lm myproc.c대신, -l 옵션을 마지막에 주면 동작합니다:
cc myproc.c -lm라이브러리 파일을 먼저 주면, linker는 이 라이브러리에서 제공하는 정의들이 나중에 쓰일지 쓰이지 않을 지 알 수가 없습니다. (아직 이 라이브러리에 있는 함수를 쓰는 오브젝트 파일을 발견하지 못했기 때문) 질문 13.28을 보기 바랍니다.
포함하는 헤더 파일을 줄인다고 해결될 문제는 아닙니다. 왜냐하면, 선언된 함수를 줄인다고 해서 (보통 헤더 파일을 선언하고 그 헤더 파일에서 제공하는 함수를 쓰지 않는 경우), 여러분의 실행 파일에 포함되는 함수 정의와는 관계가 없기 (왜냐하면 쓰이지 않았기 때문에) 때문입니다. 덧붙여 질문 13.25도 참고하시기 바랍니다.
힘든 방법이지만, 쓰이지 않는 함수들의 의존성을 검사한 다음, 필요없는 라이브러리를 link하지 않는 방법이 있습니다. 또는, 라이브러리 제작자에게 라이브러리를 좀 더 깔끔하게 만들어달라고 요구할 수 있습니다.
보통 라이브러리는 여러 개의 오브젝트 파일을 묶은 형태로 존재합니다. 기능이 떨어지는 linker를 쓴다면, 위에서 설명한 것처럼 라이브러리 내용을 모두 실행 파일에 포함시키겠지만, 기능이 뛰어난 linker는 (라이브러리 안에 존재하는) 오브젝트 파일 단위로 link합니다. 따라서 의존성이 여러 오브젝트 파일에 거쳐서 존재하지 않은 한, 위와 같은 현상은 드물게 발생합니다. 물론 나중에는 정말로 뛰어난 linker가 나와서 이런 문제가 전혀 없을 수 있지만, 아직은 그런 때가 아닌 것 같습니다.
비슷한 이유에서 GNU C Library(glibc) 소스를 보면, (오브젝트 파일 단위로 링크되는 것을 가정하고, 최소한의 내용만 link될 수 있게 하기 위해서) 소스 파일 하나가 한 두개의 함수 정의만 포함하는 것을 알 수 있습니다.
_end
가 정의되어 있지 않다고 링커가 에러를 내는데 이게
무엇을 의미하나요?
_end
가
정의되어 있지 않다고 에러가 나는 것은 다른 심볼(symbol)들도
정의가 되어 있지 않다는 것을 의미합니다. 정의되어 있지 않은
다른 심볼들이 있나 검사해 보기 바랍니다. 그러면 _end
가
정의되어 있지 않다고 하는 에러는 사라집니다. (덧붙여 질문
13.25,
13.26도 참고하시기 바랍니다.)
Seong-Kook Shin