이 절에서는 제목이 뜻하듯, 다른 절에 해당하지 않는 여러가지 주제를 다룹니다. 처음 두 단락에서는 여러가지 프로그래밍 테크닉과 bit나 byte 단위로 제어하는 방법을 소개합니다. 그 다음으로 효율성(efficiency)과 C 언어의 switch에 대해 다룹니다. “기타 언어 기능” 단락은 상당히 역사적인(historical) 내용이 많습니다; 이 단락은 C 언어의 기능이 왜 그렇게 제공되는지, 또 많은 사람들이 필요한 어떠한 기능이 왜 C 언어에서 제공되지 않는지를 설명합니다. 여기에 관한 사항은 C 언어와 다른 언어와의 차이점을 소개하기도 합니다.
알고리즘에 관한 것은 책 한 권으로 쓰기에도 모자랍니다. 또한 이 글은 알고리즘 설명을 목적으로 한 것이 아니기에, “알고리즘” 단란에서는 모든 C 프로그래머가 다루어야 하는 것만 소개합니다.
이 절의 질문과 단락은 다음과 같이 나눌 수 있습니다.
꼭 바이너리 파일을 써야만 한다면, 이식성을 높이기 위해, 또는 기존의 라이브러리를 쓰는 잇점을 누리기 위해, Sun의 XDR (RFC 1014)이나 OSI의 ASN.1 (CCITT X.409와 [C89] 8825 “Basic Encoding Rules”에서 참조됨), CDF, netCDF, HDF를 쓰는 것이 좋을 것입니다. 덧붙여 질문 2.12도 참고하시기 바랍니다.
int func(), anotherfunc(); struct { char *name; int (*funcptr)(); } symtab[] = { "func", func, "anotherfunc", anotherfunc, };
그 다음, 각각의 이름에 해당하는 테이블 목록을 뒤져서 해당하는 함수 포인터를 호출하면 됩니다. 덧붙여 질문 2.15, 18.14, 19.36도 참고하시기 바랍니다.
#include <limits.h> /* for CHAR_BIT */ #define BITMASK(b) (1 << ((b) % CHAR_BIT)) #define BITSLOT(b) ((b) / CHAR_BIT) #define BITSET(a, b) ((a)[BITSLOT(b)] |= BITMASK(b)) #define BITTEST(a, b) ((a)[BITSLOT(b)] & BITMASK(b))(만약
<limits.h>
가 없다면 CHAR_BIT
대신
8을 쓰기 바랍니다.)
int x = 1; if (*(char *)&x == 1) printf("little-endian\n"); else printf("big-endian\n");union을 써서도 가능합니다.
소스 코드에서 10진수가 아닌 표현으로는, 8진수를 표기하기 위해 수치 앞에 0을 붙이거나, 16진수를 표현하기 위해 0x를 붙이는 것이 있습니다. 실제 I/O를 처리할 때에는 이러한 수치해석은 printf와 scanf 계통의 함수들의 포맷에서 (format specifier) 지정합니다 (%d, %o, %x 등). 또는 strtol(), strtoul() 함수의 세번째 인자로도 지정할 수 있습니다. 어떤 진수로 수치를 문자열로 변환하고자 한다면 여러분 스스로 이러한 일을 하는 함수를 만들어야 합니다 (기능상 strtol 함수와 반대되는 일을 해야 합니다.). 바이너리(binary) I/O를 할 때에는 이러한 작업이 전혀 불필요합니다.
바이너리 I/O에 관한 것은 질문 2.11을 참고하기 바랍니다. 덧붙여 질문 8.6, 13.1도 참고하시기 바랍니다.
예를 들어 전 세계에서 가장 최적화된 문자 복사 코드는 문자를 전혀 복사하지 않은 코드보다 느립니다. 원문을 그대로 옮기면 다음과 같습니다:
For example, the most microoptimized character-copying loop in the world will be beat by code which avoids having to copy characters at all.
효율성을 생각할 때는, 멀리서 바라보아야 합니다. 무엇보다도, “얼마나 효과적인가”가 인기있는 이야깃거리가 될 수 있지만, 사람들이 항상 그렇게 생각하는 것은 아닙니다. 대부분의 코드는 시간에 민감할(time-critical) 필요가 없습니다. 대신 얼마나 코드가 읽기 쉬운가, 또 코드의 이식성이 높은지가 관심사일 때가 더 많습니다. (컴퓨터는 매우 빠른 기계라는 것을 잊으면 안됩니다. “비효과적인” 코드라도 효과적인 컴파일을 거치면 문제가 없는 경우가 많습니다.)
프로그램에서 어떤 부분이 가장 중요한 부분(hot spot)인지 미리 짐작하는 것은 매우 어렵습니다. 효율성이 중요한 문제가 된다면 프로파일링(profiling) 소프트웨어를 쓰면 좋습니다. 때때로 실제 시간은 주변 장치의 일, 예를 들어 I/O나 메모리 할당과 같은 곳에서 많이 걸릴 때가 많으며, 이런 문제들은 버퍼링이나 캐시로 해결합니다.
시간에 민감한 코드를 만들어야 하더라도 코드의 미세한 부분까지 일일히 신경을 쓰는 것은 좋지 않습니다. “효과적인 코딩 방법”으로 알려져 있는 것 중에 2의 지수꼴로 나타나는 수치를 곱하는 대신 쉬프트(shift) 연산을 쓰는 것이 있습니다. 그러나, 아주 간단하거나 기초적인 컴파일러라도 이를 자동으로 처리해 주는 경우가 대부분입니다. 최적화한다고 코드에 여러가지를 덕지덕지 끼워넣는다면 오히려 전체적인 속도가 떨어지는 경우가 대부분이며, 또한 이식성이 떨어지게 됩니다 (다시 말하면, 어떤 시스템에서는 빠르게 동작하더라도 다른 시스템에서는 오히려 느려지는 경우를 말합니다). 어떠한 경우라도 코드를 이리저리 바꿔보는 것은 잘해봐야 linear 성능 향상을 가져올 뿐입니다. 차라리 더 좋은 알고리즘을 선택하도록 하는 것이 낫습니다.
효율성 tradeoff20.1 및 효율성이 중요한 경우, 이를 향상시키는 방법에 대한 조언을 얻고 싶다면, Kernighan과 Plauger의 The Elements of Programming Style의 Chapter 7, 그리고 Jon Bentley의 Writing Efficient Programs를 참고하기 바랍니다.
일반적으로 큰 배열을 훑어 나갈때에는 `[]' 연산보다 포인터 연산이 빠른 것으로 알려져 있지만, 어떤 프로세서에서는 반대입니다.
함수 호출은 인라인(in-line) 코드보다 느린 것이 확실하지만 코드의 모듈화(modularity)와 명쾌함(clarity)을 생각한다면 함수 호출을 쓰는 편이 낫습니다.
`i = i + 1'과 같은 코드를 좀 더 나은 방식으로 바꾸기 전에, 여러분은 간단한 계산기를 쓰는 것이 아니라 컴파일러와 씨름한다는 것을 기억하기 바랍니다. 현대 컴파일러는 대부분 ++i, i += 1, i = i + 1과 같은 것은 모두 똑같은 코드를 만들어 냅니다. 즉 현대에 ++i, i += 1, i = i + 1 중 어떤 것을 쓰느냐는 문제는 스타일에 관한 문제이지, 더 이상 효율성에 관한 문제가 아닙니다. (덧붙여 질문 3.12도 참고하시기 바랍니다.)
i /= 2
조차도 shift로 대체시키지 못하는군요.
a ^= b; b ^= a; a ^= b;그러나 이와 같은 코드는 현대의 HLL20.2에는 맞지 않습니다. 임시 변수는 꼭 필요한 것이며, 다음과 같이 쓰는 것은,
int t = a; a = b; b = t;코드를 읽는 사람이 이해하기 쉽도록 만들어 줄 뿐만 아니라, 컴파일러가 이를 이해해서 가장 효과적인 코드를 만들 수 있도록 해 줍니다 (가능하다면 swap 명령을 써서). 게다가 이렇게 사용하면 두 변수가 포인터, 실수와 같은 타입일 때도 사용할 수 있습니다. 당연히 XOR를 사용한 방법은 이와 같은 데이터 타입에는 쓸 수 없습니다. 덧붙여 질문 3.3b, 10.3도 참고하시기 바랍니다.
if/else
를 반복해서
비교해야 합니다. 덧붙여 질문
10.12,
20.18,
20.29도 참고하시기 바랍니다.
일반 수식이나 상수가 아닌 수식을 사용하려면, if/else
를
사용해야 합니다.
switch (c) { case 1: case 2: case 3: /* case 1-3: some statements */ case 4: case 5: /* case 4-5: some statements */ default: /* ... */ }
예전에 C 초창기에는 이 괄호가 필요했습니다. 그래서 많은 사람들이 C 언어를 쓸 때, 여전히 이 괄호를 사용하곤 합니다.
(마찬가지의 이유로 sizeof 연산자에서도 괄호를 쓰곤 하지만 사실은 이 괄호도 생략 가능합니다.)
#ifdef
나 #if
를 쓰는 것이 좋습니다
(그러나 질문
11.19를 꼭 읽어보기 바랍니다.)
문자열 “/*
”와 “*/
”는 특별한 의미가 없습니다.
따라서 문자열 안에서는 주석을 쓸 수 없습니다.
왜냐하면 -- 특히 C 소스 코드를 출력해주는 -- 프로그램이 그러한
문자열을 출력하기를 원할 수도 있기 때문입니다.
또한 C++에서 제공하는 //
주석은 C 언어에서 쓸 수 없습니다.
따라서 C 프로그램에서는 이 주석을 쓰지 않는 습관을 길러야 합니다
(여러분의 컴파일러가 //
주석을 쓸 수 있는 확장 기능을 제공한다
하더라도 말입니다).
a ++ ++ + band cannot be parsed as a valid expression.
<assert.h>
에 정의되어 있는 매크로
함수이며
“assertion”을 테스트하기 위한 함수입니다. 이 때 `assertion'이란
프로그래머가 결정하는 어떤 가정(assumption)으로, 이 `assertion'이
어긋난다는 것은 심각한 프로그래밍 오류가 됩니다. 예를 들어
널이 아닌 포인터를 입력받는 함수는 다음과 같은 코드를 추가할
수 있습니다:
assert(p != NULL);`assertion'이 실패하면 프로그램은 강제 종료됩니다. 따라서 malloc()이나 fopen과 같이 예상할 수 있는 에러를 검사하기 위해 쓰일 수는 없습니다.
대부분 인기있는 컴퓨터에서 C/FORTRAN interface를 간편하게 해주는 cfortran.h라는 헤더 파일이 있으니 참고하기 바랍니다. zebra.desy.de에서 anonymous ftp로, 또는 http://www-zeus.desy.de/ burow/에서 얻을 수 있습니다.
C++에서는 "C" modifier가 external function 선언이 C calling convention을 사용하는 것을 지정해 줍니다.
p2c | Dave Gillespie씨가 만든, Pascal을 C로 변경해주는 프로그램입니다. 이 프로그램은 1990년 3월에 comp.sources.unix에 (Volumn 21) 게시되었습니다. 그리고 csvax.cs.caltech.edu에 익명의 (anonymous) FTP를 써서 pub/p2c-1.20.tar.Z로 받을 수 있습니다. |
ptoc | 마찬가지로 Pascal을 C 언어로 변경해주는 프로그램입니다. 이 프로그램은 Pascal로 작성되었으며, comp.sources.unix Volumn 10에 게시되었고, Volumn 13?에서 패치되었습니다. |
f2c | FORTRAN을 C로 변경해 주는 프로그램으로 Bell Labs, Bellcore, Carnegie Mellon의 사람들에 의해서 개발되었습니다. f2c에 대한 더 자세한 사항을 알고 싶으면, netlib@research.att.com이나 research!netlib.netlib.att.com으로 “send index from f2c”라는 메일을 보내시면 얻을 수 있습니다. (물론 익명 FTP로 netlib.att.com에서 netlib/f2c로 받을 수 있습니다.) |
이 FAQ 리스트의 관리자는 다른 (상업용) 변환 프로그램과 다른 잘 알려지지 않은 언어에 대한 프로그램의 목록을 유지하고 있습니다.
또, 여러 비슷한 단어를 같은 코드로 mapping시켜주는 “soundex” 알고리즘을 생각해 볼 수 있습니다. Soundex는 원래 (전화번호부 도우미 목적으로) 비슷하게 발음되는 이름을 발견하기 위한 목적으로 디자인된 것입니다. but it can be pressed into service for processing arbitrary words.
tm_hour
가 0이 될 때, DST adjustment를
주의해야 합니다.) 또는 Zeller's congruence를 (sci.math의
FAQ 리스트 참고) 쓰거나, Tomohiko Sakamoto씨의 다음 코드를
쓰면 됩니다:
int dayofweek(int y, int m, int d) /* 0 = Sunday */ { static int t[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4}; y -= m < 3; return (y + y/4 - y/100 + y/400 + t[m-1] + d) % 7; }덧붙여 질문 13.14, 20.32도 참고하시기 바랍니다.
(year % 4 == 0)
으로 윤년을 계산하는게
올바른가요?
year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)좋은 천문학 달력이나 (astronomical almanac), 다른 참고 도서를 참고하기 바랍니다. (To forestall an eternal debate: references which claim the existence of a 4000-year rule are wrong.) 덧붙여 질문 13.14, 13.14b도 참고하시기 바랍니다.
전형적인 예는 (한 출로 되어 있지만, 일단 수행되면 자신을 고치게 됩니다) 다음과 같습니다:
char *s = "char *s=%c%s%c; main() {printf(s, 34, s, 34);}"; main() { printf(s, 34, s, 34); }
(이 프로그램은 많은 다른 장르의 프로그램처럼,
#include <stdio.h>
를 포함하고 있지 않고, 큰따옴표 문자인
"
가 (ASCII에서) 34라고 가정하고 작성된 것입니다.)
register n = (count + 7) / 8; /* count > 0 assumed */ switch (count % 8) { case 0: do { *to = *from++; case 7: *to = *from++; case 6: *to = *from++; case 5: *to = *from++; case 4: *to = *from++; case 3: *to = *from++; case 2: *to = *from++; case 1: *to = *from++; } while (--n > 0); }
where count bytes are to be copied from the array pointed to by from to the memory location pointed to by to (which is a memory- mapped device output register, which is why to isn't incremented). It solves the problem of handling the leftover bytes (when count isn't a multiple of 8) by interleaving a switch statement with the loop which copies bytes 8 at a time. (Believe it or not, it is legal to have case labels buried within blocks nested in a switch statement like this. In his announcement of the technique to C's developers and the world, Duff noted that C's switch syntax, in particular its “fall through” behavior, had long been controversial, and that “This code forms some sort of argument in that debate, but I'm not sure whether it's for or against.”)
http://www.ioccc.org/index.html
수상자는 대개 Usenix 포럼에 공표되며 그 후로 net에도 게시됩니다. 과거의 (1984년 이후) 수상 작품들은 ftp.uu.net의 pub/ioccc 디렉토리에 저장되어 있습니다(질문 18.16 참고)
실제로 이 기능이 들어있는 컴파일러는 만들어진 적이 없다고 알려져 (이 문법이 어떤 식으로 쓰이는 지 기억하는 사람도 없으며) 있습니다. 이 키워드는 ANSI C에서는 제거되었습니다. (덧붙여 질문 1.12도 참고하시기 바랍니다.)
u/s/scs/C-FAQ/
에서
얻을 수 있습니다. 또는 뉴스 그룹에서 얻을 수도 있습니다;
최신 문서는 newsgroup comp.lang.c에 한달 주기로 게시됩니다.
동시에 이 문서를 (변경 사항 포함) 요약한 버전도 게시됩니다.
이 문서의 다양한 버전이 comp.answers와 news.answers에도 게시됩니다. news.answer 그룹에는 다른 FAQ 목록과 함께 이 목록이 제공되며, 이러한 목록들을 구할 수 있는 곳 중 두 곳을 들면 다음과 같습니다:
rtfm.mit.edu pub/usenet/news.answers/C-faq/ ftp.uu.net usenet/news.answers/C-faqftp를 쓸 수 없다면 rtfm.mit.edu의 메일 서버가 여러분에게 메일로 FAQ 목록을 보내줄 수 있으니 이 기능을 쓰면 됩니다: 메일 내용으로 “help”라는 한 단어를 mail-server@rtfm.mit.edu로 보내면 됩니다. 자세한 것은 news.answers의 meta-FAQ 목록을 참고하기 바랍니다.
하이퍼텍스트 (HTML) 버전은 WWW에서 제공됩니다: URL은 다음과 같습니다:
http://www.eskimo.com/~scs/C-faq/top.html모든 유즈넷 FAQ 리스트는 아래 사이트에서 얻을 수 있습니다:
http://www.faqs.org/faqs/
이 FAQ 리스트의 확장판은 Addison Wesley사에서 “C Programming FAQs: Frequently Asked Questions”라는 이름의 책으로 출판되었습니다 (ISBN 0-201-84519-9). 정정 목록은 (errata list) 다음에서 구할 수 있습니다:
http://www.eskimo.com/~scs/C-faq/book/Errta.html또는 ftp.eskimo.com의
u/s/scs/ftp/C-faq/book/Errta
로 구할 수도 있습니다.
각각의 문서들은 매달 흥미로운 질문들에 대한 답변이 아니라, 전체 질문/답변 목록을 유지하고 있습니다. 따라서 오래된 버전은 필요치 않을 것입니다.
http://www.cinsk.org/cfaqs/위 사이트에서 제공하는 형식은 postscript (.ps), portable document format (.pdf), 하이퍼 텍스트 (.html) 입니다.
Seong-Kook Shin