Subsections


20. Miscellaneous

이 절에서는 제목이 뜻하듯, 다른 절에 해당하지 않는 여러가지 주제를 다룹니다. 처음 두 단락에서는 여러가지 프로그래밍 테크닉과 bit나 byte 단위로 제어하는 방법을 소개합니다. 그 다음으로 효율성(efficiency)과 C 언어의 switch에 대해 다룹니다. “기타 언어 기능” 단락은 상당히 역사적인(historical) 내용이 많습니다; 이 단락은 C 언어의 기능이 왜 그렇게 제공되는지, 또 많은 사람들이 필요한 어떠한 기능이 왜 C 언어에서 제공되지 않는지를 설명합니다. 여기에 관한 사항은 C 언어와 다른 언어와의 차이점을 소개하기도 합니다.

알고리즘에 관한 것은 책 한 권으로 쓰기에도 모자랍니다. 또한 이 글은 알고리즘 설명을 목적으로 한 것이 아니기에, “알고리즘” 단란에서는 모든 C 프로그래머가 다루어야 하는 것만 소개합니다.

이 절의 질문과 단락은 다음과 같이 나눌 수 있습니다.

Miscellaneous Techniques
[*]20.1 - [*]20.6
Bits and Bytes
[*]20.7 - [*]20.12
Efficiency
[*]20.12 - [*]20.16
switch Statements
[*]20.16 - [*]20.18
Miscellaneous Language Features
[*]20.19 - [*]20.24
Other Languages
[*]20.25 - [*]20.27
Algorithms
[*]20.28 - [*]20.33
Trivia
[*]20.34 - [*]20.40


20.1 Miscellaneous Techniques



Q 20.1
한 함수에서 여러 개의 값을 리턴할 수 있을까요?
Answer
세 가지 방법을 쓸 수 있습니다: 하나는 함수 내부에서 고칠 수 있도록 포인터를 인자로 받는 것이고, 하나는 여러 개의 멤버를 가지는 구조체를 리턴하도록 하는 것입니다. 마지막으로 전역 변수를 이용하는 방법이 있습니다. 덧붙여 질문 [*]2.7, [*]4.8, [*]7.5a도 참고하시기 바랍니다.



Q 20.3
`command-line argument'에 접근하는 방법은?
Answer
main()에 전달되는, argv 배열에 대한 포인터를 쓰면 가능합니다. 덧붙여 질문 [*]8.2, [*]13.7, [*]19.20도 참고하시기 바랍니다.

References
[K&R1] § 5.11 pp. 110-114
[K&R2] § 5.10 pp. 114-118
[C89] § 5.1.2.2.1
[H&S] § 20.1 p. 416
[PCS] § 5.6 pp. 81-2, § 11 p. 159, pp. 339-40 Appendix F
[Dale] § 4 pp. 75-85



Q 20.5
각각 다른 워드 크기나 (word size), 다른 바이트 순서를 (byte order) 쓰는, 또는 각각 다른 실수 포맷을 (floating point format) 쓰는 컴퓨터에서 데이터 파일을 주고 받으려 합니다. 이런 데이터 파일을 만들 수 있을까요?
Answer
가장 이식성이 뛰어난 방식은 (대개 ASCII 코드로 된) 텍스트 파일을 사용하는 것입니다: fprintf()fscanf()와 같은 함수를 쓰면 됩니다. (네트워크 프로토콜 상에서도 비슷한 문제가 발생할 수 있습니다.) 그러나, 텍스트 파일은 바이너리 파일에 비해 처리 속도가 떨어지고, 같은 데이터를 포함할 때에도 크기가 훨씬 클 수 있다는 사실을 알아두어야 합니다. 이러한 단점에도 텍스트 파일로 저장할 경우, 각각 다른 환경의 컴퓨터에서도 이 데이터를 쓸 수 있다는 장점이 있고, 또 이러한 텍스트 파일을 처리하는 많은 표준 툴들이 있다는 것을 생각할 때, 텍스트 파일의 중요성은 강조해도 지나치지 않습니다.

꼭 바이너리 파일을 써야만 한다면, 이식성을 높이기 위해, 또는 기존의 라이브러리를 쓰는 잇점을 누리기 위해, Sun의 XDR (RFC 1014)이나 OSI의 ASN.1 (CCITT X.409와 [C89] 8825 “Basic Encoding Rules”에서 참조됨), CDF, netCDF, HDF를 쓰는 것이 좋을 것입니다. 덧붙여 질문 [*]2.12도 참고하시기 바랍니다.

References
[PCS] § 6 pp. 86, 88.



Q 20.6
문자열에 어떤 함수의 이름이 저장되어 있을 때, 이 문자열 만으로 함수를 호출할 수 있을까요?
Answer
가장 직접적인 방법은 각각의 함수 이름과 함수 포인터를 테이블에 저장하는 것입니다:

  int func(), anotherfunc();

  struct { char *name; int (*funcptr)(); } symtab[] = {
    "func",         func,
    "anotherfunc",  anotherfunc,
  };

그 다음, 각각의 이름에 해당하는 테이블 목록을 뒤져서 해당하는 함수 포인터를 호출하면 됩니다. 덧붙여 질문 [*]2.15, [*]18.14, [*]19.36도 참고하시기 바랍니다.

References
[PCS] § 11 p. 168


20.2 Bits and Bytes



Q 20.8
비트(bit)로 이루어진 집합 또는 배열을 만들려면 어떻게 하죠?
Answer
char 또는 int 타입의 배열을 만들고 각각의 비트를 제어하기 위한 인덱스를 사용하는 매크로를 쓰면 됩니다. char 타입을 써서 만든 예는 다음과 같습니다:
  #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을 쓰기 바랍니다.)

References
[H&S] § 7.6.7 pp. 211-216



Q 20.9
컴퓨터의 바이트 순서가 (byte order) big-endian인지 little-endian인지 어떻게 알아내죠?
Answer
한가지 방법은 포인터를 쓰는 것입니다:
  int x = 1;
  if (*(char *)&x == 1)
    printf("little-endian\n");
  else
    printf("big-endian\n");
union을 써서도 가능합니다.

덧붙여 질문 [*]10.16도 참고하시기 바랍니다.

References
[H&S] § 6.1.2 pp. 163-4



Q 20.10
정수를 2 진수나 16 진수로 바꿀수 있을까요
Answer
먼저 무엇을 질문하려 하는 지를 잘 생각하기 바랍니다. 10진, 16진, 8진, 2진 등 어떤 식의 표현이 편리할 지에 상관없이 정수는 내부적으로 2 진수로 저장됩니다. 이러한 진법이 관심 대상이 될 경우는, 실제 사용자와 인터페이스하는 부분일 (outside world) 때입니다.

소스 코드에서 10진수가 아닌 표현으로는, 8진수를 표기하기 위해 수치 앞에 0을 붙이거나, 16진수를 표현하기 위해 0x를 붙이는 것이 있습니다. 실제 I/O를 처리할 때에는 이러한 수치해석은 printfscanf 계통의 함수들의 포맷에서 (format specifier) 지정합니다 (%d, %o, %x 등). 또는 strtol(), strtoul() 함수의 세번째 인자로도 지정할 수 있습니다. 어떤 진수로 수치를 문자열로 변환하고자 한다면 여러분 스스로 이러한 일을 하는 함수를 만들어야 합니다 (기능상 strtol 함수와 반대되는 일을 해야 합니다.). 바이너리(binary) I/O를 할 때에는 이러한 작업이 전혀 불필요합니다.

바이너리 I/O에 관한 것은 질문 [*]2.11을 참고하기 바랍니다. 덧붙여 질문 [*]8.6, [*]13.1도 참고하시기 바랍니다.

References
[C89] § 7.10.1.5, 7.10.1.6.



Q 20.11
상수를 지정할 때 2진수를 (예를 들면 0b101010처럼) 쓸 수 있나요? 또 2진수를 출력하기 위한 printf() 포맷이 있나요?
Answer
두 경우 모두 없습니다. 2진수를 출력하려면 strtol() 함수를 써서 직접 문자열을 만들어야 합니다. 덧붙여 질문 [*]20.10도 참고하시기 바랍니다.


20.3 Efficiency



Q 20.12
주어진 수치에서 비트 1이 몇 개인지 세는(count) 효과적인 방법은 무엇인가요?
Answer
이러한 “비트에 관련된 귀찮은” (bit-fiddling) 문제들은 lookup 테이블을 써서 해결될 수 있습니다 (그러나 질문 [*]20.13을 꼭 참고하기 바랍니다).



Q 20.13
어떻게 하면 효과적인 프로그램을 만들 수 있을까요?

Answer
좋은 알고리즘을 사용하고, 주의깊게 구현하고, 프로그램이 불필요한 작업을 하지 않도록 하면 됩니다.

예를 들어 전 세계에서 가장 최적화된 문자 복사 코드는 문자를 전혀 복사하지 않은 코드보다 느립니다. 원문을 그대로 옮기면 다음과 같습니다:

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를 참고하기 바랍니다.



Q 20.14
정말로 포인터는 배열보다 빠르게 동작하나요? 함수 호출은 어느 정도 시간을 잡아먹죠? ++ii = i + 1보다 빠른게 확실한가요?

Answer
이런 질문들은 프로세서와 컴파일러에 따라 답이 달라집니다. 간단히 알고 싶다면, 테스트 프로그램을 만들어 시간을 재면 됩니다. 그러나 그 차이가 매우 적기 때문에 같은 프로그램을 수천번 돌려야 할지도 모릅니다. 만약 컴파일러가 어셈블리 언어 출력을 지원한다면, 두 가지 코드가 같은 방식으로 컴파일되는지 먼저 확인하기 바랍니다.

일반적으로 큰 배열을 훑어 나갈때에는 `[]' 연산보다 포인터 연산이 빠른 것으로 알려져 있지만, 어떤 프로세서에서는 반대입니다.

함수 호출은 인라인(in-line) 코드보다 느린 것이 확실하지만 코드의 모듈화(modularity)와 명쾌함(clarity)을 생각한다면 함수 호출을 쓰는 편이 낫습니다.

`i = i + 1'과 같은 코드를 좀 더 나은 방식으로 바꾸기 전에, 여러분은 간단한 계산기를 쓰는 것이 아니라 컴파일러와 씨름한다는 것을 기억하기 바랍니다. 현대 컴파일러는 대부분 ++i, i += 1, i = i + 1과 같은 것은 모두 똑같은 코드를 만들어 냅니다. 즉 현대에 ++i, i += 1, i = i + 1 중 어떤 것을 쓰느냐는 문제는 스타일에 관한 문제이지, 더 이상 효율성에 관한 문제가 아닙니다. (덧붙여 질문 [*]3.12도 참고하시기 바랍니다.)



Q 20.15b
스피드 향상을 위해 어셈블러를 사용할 필요가 없다는 이유로 최적화 컴파일러가 (optimizing compiler) 좋다고 합니다. 그러나 제 최적화 컴파일러는 i /= 2조차도 shift로 대체시키지 못하는군요.
Answer
isigned입니까, 아니면 unsigned입니까? 만약 signed 타입이었다면, `shift' 연산과 같지 않습니다. (힌트: i가 음수이고 홀수일 경우를 생각해보기 바랍니다.) 그래서 컴파일러가 `shift' 연산을 쓰지 않았을 것입니다.



Q 20.15c
임시 저장 변수 없이 두 변수의 내용을 바꿀 수 있을까요?

Answer
오래된 어셈블리 프로그래머들의 트릭을 쓰면 다음과 같이 만들 수 있습니다:
  a ^= b;
  b ^= a;
  a ^= b;
그러나 이와 같은 코드는 현대의 HLL20.2에는 맞지 않습니다. 임시 변수는 꼭 필요한 것이며, 다음과 같이 쓰는 것은,

  int t = a;
  a = b;
  b = t;
코드를 읽는 사람이 이해하기 쉽도록 만들어 줄 뿐만 아니라, 컴파일러가 이를 이해해서 가장 효과적인 코드를 만들 수 있도록 해 줍니다 (가능하다면 swap 명령을 써서). 게다가 이렇게 사용하면 두 변수가 포인터, 실수와 같은 타입일 때도 사용할 수 있습니다. 당연히 XOR를 사용한 방법은 이와 같은 데이터 타입에는 쓸 수 없습니다. 덧붙여 질문 [*]3.3b, [*]10.3도 참고하시기 바랍니다.


20.4 switch Statements



Q 20.17
문자열에 대해 switch를 쓸 수 있을까요?
Answer
직접적으로는 쓸 수 없습니다. 그러나, 문자열을 정수로 매핑시키는 함수를 써서 이 함수를 switch에 쓰면 가능합니다. 그렇지 않다면 strcmp()if/else를 반복해서 비교해야 합니다. 덧붙여 질문 [*]10.12, [*]20.18, [*]20.29도 참고하시기 바랍니다.
References
[K&R1] § 3.4 p. 55
[K&R2] § 3.4 p. 58
[C89] § 6.6.4.2
[H&S] § 8.7 p. 248



Q 20.18
case label에 상수가 아닌 표현 (예를 들어 어떤 범위나 일반 수식)을 사용하는 방법이 있을까요?

Answer
없습니다. switch statement는 원래 컴파일러가 매우 간단하게 번역할 수 있도록 디자인되었기 때문에, case label은 단일한(single), 상수(constant) 이면서, 정수 수식(integral expression)만 사용할 수 있습니다. 물론 하나의 statement에 여러 case label을 붙이는 것으로 간단한 범위를 흉내낼 수 있습니다. (아래 예제 참고).

일반 수식이나 상수가 아닌 수식을 사용하려면, if/else를 사용해야 합니다.

덧붙여 질문 [*]20.17도 참고하시기 바랍니다.

Note
  switch (c) {
  case 1:
  case 2:
  case 3:
    /* case 1-3: some statements */
  case 4:
  case 5:
    /* case 4-5: some statements */
  default:
    /* ...  */
  }
References
[K&R1] § 3.4 p. 55
[K&R2] § 3.4 p. 58
[C89] § 6.6.4.2
[ANSI Rationale] § 3.6.4.2
[H&S] § 8.7 p. 248


20.5 Miscellaneous Language Features



Q 20.19
return 문장에서 바깥쪽(outter) 괄호는 정말로 생략 가능한가요?
Answer
생략 가능합니다 (optional).

예전에 C 초창기에는 이 괄호가 필요했습니다. 그래서 많은 사람들이 C 언어를 쓸 때, 여전히 이 괄호를 사용하곤 합니다.

(마찬가지의 이유로 sizeof 연산자에서도 괄호를 쓰곤 하지만 사실은 이 괄호도 생략 가능합니다.)

References
[K&R1] § A18.3 p. 218
[C89] § 6.3.3, § 6.6.6
[H&S] § 8.9 p. 254



Q 20.20
왜 C 언어의 주석(comment)는 중첩해서 쓸 수 없을까요? 주석이 들어있는 코드의 일부분을 주석 처리하고 싶거든요. 또, 문자열 안에 주석을 쓸 수 있나요?

Answer
C 언어의 주석은 중첩해서 쓸 수 없습니다. C 언어의 주석은 PL/I의 주석 구문을 빌어왔으며, PL/I에서는 중첩된 주석문을 허용하지 않습니다. 따라서 -- 내부적으로 주석을 포함한 -- 어떠한 코드 블럭을 주석 처리하고 싶다면, #ifdef#if를 쓰는 것이 좋습니다 (그러나 질문 [*]11.19를 꼭 읽어보기 바랍니다.)

문자열 “/*”와 “*/”는 특별한 의미가 없습니다. 따라서 문자열 안에서는 주석을 쓸 수 없습니다. 왜냐하면 -- 특히 C 소스 코드를 출력해주는 -- 프로그램이 그러한 문자열을 출력하기를 원할 수도 있기 때문입니다.

또한 C++에서 제공하는 // 주석은 C 언어에서 쓸 수 없습니다. 따라서 C 프로그램에서는 이 주석을 쓰지 않는 습관을 길러야 합니다 (여러분의 컴파일러가 // 주석을 쓸 수 있는 확장 기능을 제공한다 하더라도 말입니다).

References
[K&R1] § A2.1 p. 179
[K&R2] § A2.2 p. 192
[C89] § 6.1.9, Annex F
[ANSI Rationale] § 3.1.9
[H&S] § 2.2 pp. 18-9
[PCS] § 10 p. 130



Q 20.20b
Is C a great language, or what? Where else could you write something like a+++++b ?

Answer
Well, you can't meaningfully write it in C, either. The rule for lexical analysis is that at each point during a straightforward left-to-right scan, the longest possible token is determined, without regard to whether the resulting sequence of tokens makes sense. The fragment in the question is therefore interpreted as
  a ++ ++ + b
and cannot be parsed as a valid expression.

References
[K&R1] § A2 p. 179
[K&R2] § A2.1 p. 192
[C89] § 6.1
[H&S] § 2.3 pp. 19-20



Q 20.24
C 언어에서는 왜 중첩된 함수를 제공하지 않을까요?
Answer
중첩된 함수에서 지역 변수와 함수를 제어하는 것은 쉽지 않기 때문입니다. 따라서 C 언어에서는 간결화(simplification)를 추구하기 위해 이 기능을 제공하지 않습니다. (gcc는 확장 기능으로 중첩 함수를 제공합니다.) 대개 이러한 기능은 qsort에서 비교 함수를 만들기 위해 필요하다고 생각할 수 있습니다. 이런 함수들은 static 선언과 정적 변수를 써서 만들면 됩니다. (깨끗한 해결책으로는 필요한 정보를 가지고 있는 구조체에 대한 포인터를 전달하는 것이지만, qsort()에서는 제공하지 않습니다.)



Q 20.24b
assert()은 어떤 함수인가요?
Answer
assert() 함수는 <assert.h>에 정의되어 있는 매크로 함수이며 “assertion”을 테스트하기 위한 함수입니다. 이 때 `assertion'이란 프로그래머가 결정하는 어떤 가정(assumption)으로, 이 `assertion'이 어긋난다는 것은 심각한 프로그래밍 오류가 됩니다. 예를 들어 널이 아닌 포인터를 입력받는 함수는 다음과 같은 코드를 추가할 수 있습니다:
  assert(p != NULL);
`assertion'이 실패하면 프로그램은 강제 종료됩니다. 따라서 malloc()이나 fopen과 같이 예상할 수 있는 에러를 검사하기 위해 쓰일 수는 없습니다.
References
[K&R2] § B6 pp. 253-4
[C89] § 7.2
[H&S] § 19.1 p. 406


20.6 Other Languages



Q 20.25
C 언어에서 FORTRAN (C++, BASIC, Pascal Ada, LISP) 함수를 부를 수 있을까요? (반대로 다른 언어가 C 함수를 가져다 쓸 수 있을까요?)
Answer
답은 전적으로 사용하고 있는 컴퓨터 기종과 컴파일러가 사용하는 `calling sequence'에 따라 달려 있습니다. (전혀 불가능할 수도 있습니다.) 일단 컴파일러 문서를 주의깊게 읽어보시기 바랍니다. 대개는 “mixed-language programming guide”라는 이름의 문서가 함께 제공될 것이니, 이를 참고하기 바랍니다. 올바른 방법으로 인자를 전달하고 적절한 startup code를 사용해야 하지만, 그리 쉬운 것이 아님을 미리 밝혀 둡니다. 좀 더 자세한 것은 Gleen Geers씨의 FORT.gz 파일을 참고하기 바라며, ftp로 suphys.physics.su.oz.au의 src 디렉토리에서 얻을 수 있습니다.

대부분 인기있는 컴퓨터에서 C/FORTRAN interface를 간편하게 해주는 cfortran.h라는 헤더 파일이 있으니 참고하기 바랍니다. zebra.desy.de에서 anonymous ftp로, 또는 http://www-zeus.desy.de/ burow/에서 얻을 수 있습니다.

C++에서는 "C" modifier가 external function 선언이 C calling convention을 사용하는 것을 지정해 줍니다.

References
[H&S] § 4.9.8 pp. 106-7



Q 20.26
Pascal이나 FORTRAN (또는 LISP, Ada, awk, 구 스타일 C 등)을 C 언어로 변환해주는 프로그램이 있을까요?
Answer
자유롭게 배포되는 프로그램들은 다음과 같습니다:

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 리스트의 관리자는 다른 (상업용) 변환 프로그램과 다른 잘 알려지지 않은 언어에 대한 프로그램의 목록을 유지하고 있습니다.

덧붙여 질문 [*]11.31, [*]18.16도 참고하시기 바랍니다.



Q 20.27
C++은 C의 `superset'입니까? C 코드를 컴파일하기 위해 C++ 컴파일러를 써도 상관없을까요?
Answer
C++는 C 언어에서 유래한 언어입니다. 그리고 실제로 많은 부분을 C 언어에 기초를 두고 있습니다만, 어떤 부분에 대해서는 C 언어 관점으로 올바른 것이 C++에서는 올바르지 않은 경우가 있습니다. 거꾸로, ANSI C에는 몇가지 기능들을 (프로토타입, const, 등) C++에서 가져왔습니다. 따라서 한 언어가 다른 언어의 완전한 `superset'은 아닙니다; 또 어떤 의미를 정의할 때, 서로 다르게 정의한 부분도 있습니다. 이런 차이점에도 불구하고, 많은 C 프로그램들이 C++에서도 올바르게 동작합니다. 그리고 대부분의 최근 컴파일러들은 C와 C++ 컴파일 모드를 다 지원합니다. 덧붙여 질문 [*]8.9, [*]20.20도 참고하시기 바랍니다.
References
[H&S] p. xviii, § 1.1.5 p. 6, § 2.8 pp. 36-7, § 4.9 pp. 104-107


20.7 Algorithms



Q 20.28
strcmp와 비슷한 루틴이 필요한데, 두 문자열이 정확하게 일치하지 않는 경우도 일치하는 것으로 처리하는 기능이 필요합니다.
Answer
대충 비슷한 문자열을 비교하기(approximate string matching) 위한 정보와 알고리즘은 Sun Wu와 Udi Manber's Paper의 “AGREP -- A Fast Apporoximate Pattern-Matching Tool”에서 찾아볼 수 있습니다.

또, 여러 비슷한 단어를 같은 코드로 mapping시켜주는 “soundex” 알고리즘을 생각해 볼 수 있습니다. Soundex는 원래 (전화번호부 도우미 목적으로) 비슷하게 발음되는 이름을 발견하기 위한 목적으로 디자인된 것입니다. but it can be pressed into service for processing arbitrary words.

References
[Knuth] § 6 pp. 391-2 Vol. 3
[Wu]



Q 20.29
해싱(hashing)이란 무언가요?
Answer
해싱(hashing)은 문자열을 (대개 범위가 작은) 정수로 대응시키는 (mapping) 작업을 의미합니다. “해시 함수”는 (hash function) 문자열을 (또는 다른 data structure) 어떤 범위를 가지는 수치로 (hash bucket) 매핑시키는 함수입니다. 이 수치는 대개 배열의 인덱스 값으로 사용하거나, 반복되는 비교를 처리하기 위해 사용합니다. (물론, 상당히 큰 문자열의 집합을 작은 범위의 수치로 매핑한다면, 일대일의 관계가 성립하지 않을 수도 있습니다. 따라서 해싱을 처리하는 알고리즘은 이런 “collision” 발생을 적절하게 처리해 주어야 합니다.) 이미 해시에 관한 알고리즘과 함수가 많이 개발되었습니다; 이런 주제에 대한 더 자세한 것은 이 리스트의 목적과 맞지 않기 때문에 생략합니다.

References
[K&R2] § 6.6
[Knuth] § 6.4 pp. 506-549 Vol. 3
[Robert] § 16 pp. 231-244



Q 20.31
주어진 날짜로 요일을 계산하려면 어떻게 하죠?
Answer
mktime()localtime()을 쓰면 됩니다. (덧붙여 질문 [*]13.13, [*]13.14도 참고하시기 바랍니다.. 그러나 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도 참고하시기 바랍니다.

References
[C89] § 7.12.2.3.



Q 20.32
2000년이 윤년인가요? (year % 4 == 0)으로 윤년을 계산하는게 올바른가요?
Answer
2000년은 윤년이 (leap year) 맞습니다. 그리고 윤년을 계산하는 공식은 다음과 같습니다:
  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도 참고하시기 바랍니다.


20.8 Trivia



Q 20.34
자신의 소스 코드를 출력하는 프로그램을 만들려면 어떻게 하죠?
Answer
이식성이 뛰어난, 자신을 재 출력하는 프로그램을 (self-reproducing program) 만드는 것은 매우 어려운 일입니다. 특히 따옴표와 문자 코드 때문에 어렵습니다.

전형적인 예는 (한 출로 되어 있지만, 일단 수행되면 자신을 고치게 됩니다) 다음과 같습니다:

char *s = "char *s=%c%s%c; main() {printf(s, 34, s, 34);}";
main() { printf(s, 34, s, 34); }

(이 프로그램은 많은 다른 장르의 프로그램처럼, #include <stdio.h>를 포함하고 있지 않고, 큰따옴표 문자인 "가 (ASCII에서) 34라고 가정하고 작성된 것입니다.)



Q 20.35
What is “Duff's Device”?

Answer
It's a devastatingly deviously unrolled byte-copying loop, devised by Tom Duff while he was at Lucasfilm. In its “classic” form, it looks like:

  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.”)



Q 20.36
다음 “International Obfuscated C Code Contest(IOCCC)”는 언제 열리나요? 저번 수상자의 작품을 볼 수 있을까요?

Answer
이 컨테스트는 항상 진행 중입니다. 다음 사이트를 참고하기 바랍니다:
  http://www.ioccc.org/index.html

수상자는 대개 Usenix 포럼에 공표되며 그 후로 net에도 게시됩니다. 과거의 (1984년 이후) 수상 작품들은 ftp.uu.netpub/ioccc 디렉토리에 저장되어 있습니다(질문 [*]18.16 참고)



Q 20.37
[K&R1]에 나오는 entry 키워드는 어디에 쓰는 거죠?
Answer
함수가 - FORTRAN에서처럼 - 하나 이상의 entry point를 가질 수 있도록 하기 위해 예약되어 있는 것입니다.

실제로 이 기능이 들어있는 컴파일러는 만들어진 적이 없다고 알려져 (이 문법이 어떤 식으로 쓰이는 지 기억하는 사람도 없으며) 있습니다. 이 키워드는 ANSI C에서는 제거되었습니다. (덧붙여 질문 [*]1.12도 참고하시기 바랍니다.)

References
[K&R2] p. 259 Appendix C.



Q 20.38
“C” 언어의 “C”는 어디에서 유래한 말인가요?
Answer
C 언어는 Ken Thompson씨의 실험 언어인 B 언어에서 유래한 것입니다. 그리고 이 B 언어는 Martin Richards씨의 BCPL에서 (Basic Combined Programming Language) 유래한 것입니다. 이 BCPL은 CPL을 (Cambridge Programming Language) 간략화한 (simplification) 언어입니다. C 언어를 상속한 P 언어도 있습니다; `D'가 아닌 `P'를 선택한 것은 BCPL의 세번째 글자가 `P'이기 때문입니다. 그러나, 현재 C 언어를 상속한 가장 잘 알려진 언어는 C++입니다.



Q 20.39
char”를 어떻게 발음하죠?
Answer
C 키워드(keyword)인 “char”는 세 가지의 발음을 쓸 수 있습니다: 영어 단어인 “char”, “care”, “car” (또는 “character”로); 어떤 것을 쓰느냐는 별로 중요하지 않습니다.



Q 20.39b
“lvalue”와 “rvalue”는 무엇인가요?
Answer
간단히 말해서, “lvalue”는 대입(assignment) 연산에서 왼쪽에 올 수 있는 수식을 말합니다; 따라서 어떤 위치를 나타내는 오브젝트로 생각할 수 있습니다. (그러나 질문 [*]6.7을 참고하기 바랍니다.) “rvalue”라 함은 어떠한 값을 가지는 모든 수식을 말합니다 (즉 대입 연산에서 오른쪽에 올 수 있는 수식을 말합니다).



Q 20.40
이 문서의 사본은 어디에서 얻을 수 있죠? 이전 버전도 얻을 수 있나요?
Answer
가장 최신의 사본은 ftp.eskimo.comu/s/scs/C-FAQ/에서 얻을 수 있습니다. 또는 뉴스 그룹에서 얻을 수도 있습니다; 최신 문서는 newsgroup comp.lang.c에 한달 주기로 게시됩니다. 동시에 이 문서를 (변경 사항 포함) 요약한 버전도 게시됩니다.

이 문서의 다양한 버전이 comp.answersnews.answers에도 게시됩니다. news.answer 그룹에는 다른 FAQ 목록과 함께 이 목록이 제공되며, 이러한 목록들을 구할 수 있는 곳 중 두 곳을 들면 다음과 같습니다:

  rtfm.mit.edu  pub/usenet/news.answers/C-faq/
  ftp.uu.net    usenet/news.answers/C-faq
ftp를 쓸 수 없다면 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.comu/s/scs/ftp/C-faq/book/Errta로 구할 수도 있습니다.

각각의 문서들은 매달 흥미로운 질문들에 대한 답변이 아니라, 전체 질문/답변 목록을 유지하고 있습니다. 따라서 오래된 버전은 필요치 않을 것입니다.

Note
이 문서의 한국어판은 아래 URL에서 얻을 수 있습니다:
  http://www.cinsk.org/cfaqs/
위 사이트에서 제공하는 형식은 postscript (.ps), portable document format (.pdf), 하이퍼 텍스트 (.html) 입니다.

Seong-Kook Shin
2018-05-28