-
Notifications
You must be signed in to change notification settings - Fork 4
/
ext.tex
448 lines (368 loc) · 14.7 KB
/
ext.tex
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
\chapter{Extensions} \label{chap:ext}
% chapter 21: ext
이 chapter에서는 역자가 추가한 질문들을 정리해 놓았습니다.
\section{miscellaneous code} \label{sec:misccode}
이 단락에서는 여러분들이 거의 모든 프로그램 개발에 쓸 수 있는 utility
함수들에 대한 설명입니다.
\begin{faq} \label{faq:error}
\Q{21.1} % 21.1
라이브러리 함수들이 성공적으로 수행되었는지 일일이 리턴값을 조사해서
에러 메시지를 출력하는 것이 번거롭습니다. 간단히 할 수 있는 방법이
없을까요?
\A
다음과 같이 error 함수\footnote{이 \TT{error} 함수는 GNU software
소스 코드에서 얻은 것을 간략화한 것입니다.}를 만들기 바랍니다.
% TODO: error()가 어떤 GNU software에서 온 것인지 출처를 정확히 밝힐 것.
\begin{verbatim}
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* 아래 변수는 error()가 몇 번 불렸는지 횟수를
* 저장합니다. */
unsigned int error_message_count;
/* 아래 함수 포인터가 NULL이면 error() 함수는
* stdout을 flush시키고 stderr로 프로그램 이름을
* 출력하고, ": "를 출력합니다. */
void (*error_print_progname);
/* error() 함수를 쓰는 프로그램은 반드시
* program_name이라는 변수를 정의하고 프로그램의
* 이름(실행 파일 이름)을 지정해야 합니다. */
extern const char *program_name;
void
error(int status, int errnum, const char *msg, ...)
{
va_list argptr;
if (error_print_progname)
(*error_print_progname)();
else {
fflush(stdout);
fprintf(stderr, "%s: ", program_name);
}
va_start(argptr, msg);
vfprintf(stderr, msg, argptr);
va_end(argptr);
++error_message_count;
if (errnum)
fprintf(stderr, ": %s", strerror(errnum));
putc('\n', stderr);
fflush(stderr);
if (status)
exit(status);
}
\end{verbatim}
\noindent 일단 위와 같이 error() 함수를 만들었으면 여러분의
코드에서 다음과 같이 쓸 수 있습니다.
\begin{verbatim}
#include <stdio.h>
#include <errno.h>
const char *program_name = "sample"
int
main(void)
{
FILE *fp;
char *filename = "sample.dat";
fp = fopen(filename, "r");
if (fp == NULL)
error(EXIT_FAILURE, errno,
"cannot open file %s.", filename);
/* ... */
}
\end{verbatim}
\noindent 위의 코드를 설명하겠습니다.
\TT{error()}의 첫번째 인자는 0이 아닌 경우,
라이브러리 함수 \TT{exit()}로 전달됩니다. 따라서
\verb+<stdlib.h>+에 정의된 0이 아닌 값을 가지는
\TT{EXIT\_FAILURE} 상수를 써서 \TT{error()}가 메시지를
출력하고 프로그램을 종료하도록 합니다
\TT{error()}에 전달된 두번째 인자인,
\TT{errno}는 \verb+<errno.h>+에 정의된 전역 변수로서
몇몇 라이브러리 함수들이 보고한 에러의 종류를 나타내는
변수입니다. 이 값은 \TT{error()}가 \TT{strerror} 함수를
써서 에러를 나타내는 문자열로 바꾼 다음 출력합니다.
시스템과 라이브러리 함수가 보고한 에러가 아니라면
\TT{error()}의 두번째 인자로 0을 주면 됩니다.
\TT{error()}의 세번째 인자부터는 \TT{printf()}가 쓰는
형식 그대로 쓰시면 됩니다.
물론 위의 예제가 완벽하지는 않습니다. 여러분은 \TT{error()}
함수를 쓰기 위해, 앞에서 제시한 \TT{error()} 정의와,
이 함수가 쓰는 전역 변수들을 적당하게 추가해야 합니다.
에러 함수의 선언과 \TT{va\_list}, \TT{va\_start()}, \TT{va\_end()}에
관한 것은 \ref{chap:vlal} 절을 참고하기 바랍니다.
\R
% TODO: error()가 어떤 GNU software에서 온 것인지 출처를 정확히 밝힐 것.
\cite{glibc} \TT{misc/error.*}
\end{faq}
\begin{faq} \label{str2int}
\Q{21.2} % 21.2
User input을 string으로 받은 다음, \TT{atoi} 함수를 써서
수치로 바꾸어 작업 했습니다. 그런데 가끔 이상한 문제가
발생합니다. 왜 그럴까요?
\A
\TT{atoi}는 주어진 문자열을 10진수로 해석해서
\TT{int}로 바꾸어 주지만,
에러는 처리해 주지 않습니다.
다시 말해서 입력 문자열을 10진수로 처리할 수 없을 때에는
대개 0을 리턴합니다.
% TODO: atoi가 에러를 만난 경우 0을 리턴하는 것이 표준인가 확인!!!!
따라서 다음과 같은 함수를 만들어 쓰는 것이 좋습니다:
\begin{verbatim}
#include <errno.h>
int
convint(long int *d, const char *s)
{
char *p;
long int i;
if (*s == '\0') /* error: nothing in the source string */
return -1;
i = strtol(s, &p, 0);
if (errno == ERANGE) /* error: underflow or overflow */
return -1;
if (*p != '\0') /* error: there is unexpected character */
return -1;
*d = i;
return 0;
}
\end{verbatim}
위 함수는 두 개의 인자를 받습니다. 첫 인자는 변환한 \TT{long int}를
저장할
포인터를 받고, 두 번째 인자는 변환할 문자열을 받습니다.
리턴 값은 성공적으로 변환한 경우에는 0을, 그렇지 않은 경우에는 $-1$을
리턴합니다 -- 입력에 예상할 수 없는 문자값이 오거나, 입력이 아예
없거나(문자열이 \verb+'\0'+인 경우), 변환한 값이 \TT{long int}에
담을 수 없을 만큼 크거나 작은 경우, 에러로 처리합니다.
실제 사용한 예는 다음과 같습니다:
\begin{verbatim}
void
foo(void)
{
char instr[80];
int i;
/* instr이 user input을 가지고 있다고 가정 */
if (convint(&i, instr) < 0) {
/* error: invalid user input */
}
/* ... */
}
\end{verbatim}
주의할 것은 이 \TT{convint} 함수가 \TT{atoi}와 똑같이 동작하는 게
아니라는 것입니다 --- 단순히 리턴값과 인자만의 문제가 아닙니다.
\TT{atoi}와 \TT{convint}의 다른 점은 크게 다음과 같습니다:
\begin{enumerate}
\item
\TT{atoi}는 에러를 검사하지 않습니다.
따라서 입력이 \verb+"12xyz"+인 경우에 12를 리턴합니다.
\TT{convint}는 입력 문자열이 완전한 경우에만 처리하므로,
입력이 \verb+"12xyz"+인 경우에는 \TT{int} 변환을 수행하지 않습니다
--- 즉 에러로 처리합니다.
\item
\TT{atoi}의 리턴 타입이 \TT{int}인 반면, \TT{convint}의 경우
\TT{long int}입니다.
\item
\TT{atoi}는 10진수만 처리할 수 있는 반면, \TT{convint}는
\TT{strtol}의 세번째 인자로 0을 주었기 때문에, 8진수나 16진수도
처리할 수 있습니다; 16진수의 경우 문자열 앞에 \verb+"0x"+를 붙여야
하며, 8진수의 경우에 문자열 앞에 \verb+"0"+을 붙여야 합니다.
% TODO: strtol의 세번째 인자를 0으로 한 것이 표준에 맞는지 확인!!!
\end{enumerate}
\end{faq}
\begin{faq}
\Q{21.6}
주어진 파일(\TT{FILE *})에서 한 줄씩 읽으려고 합니다. 질문 \ql{12.23}에
따르면 \TT{gets}를 쓰는 것은 좋지 않다고 했는데, 그럼 어떻게 해야 하나요?
\TT{fgets}을 쓰면, 버퍼의 크기를 지정해야 하기 때문에, 파일의 한 줄이
지정한 버퍼보다 긴 경우, 잘려나가 버립니다.
\A
이 문제를 해결하기 위한 표준 함수는 존재하지 않습니다. 아주 긴 줄도
읽기 위해서는, 버퍼의 크기를 미리 지정하지 않고, 필요할 경우 늘려나가는
방식을 써야 합니다. 표준 함수는 아니지만, \cite{glibc}는 이러한 일을
처리하기 위해 \TT{getline} 함수를 제공합니다. 좀 더 향상된 기능이 필요하다면
GNU readline 패키지를 써 보기 바랍니다:
\begin{verbatim}
http://cnswww.cns.cwru.edu/php/chet/readline/rltop.html
\end{verbatim}
\TT{getline} 함수는, 미리 \TT{malloc} 또는 \TT{realloc}을 써서
할당한 버퍼와 그 크기를 입력받아서, 한 줄을 읽어 버퍼에 저장해 줍니다.
이 때 \verb+\n+ 문자도 포함합니다.
만약 버퍼의 크기가 부족하면 \TT{realloc}을 불러서, 버퍼의 크기를 자동으로
키워 줍니다. EOF를 만나거나 에러가 발생하면 -1을 리턴하고, 성공한 경우에는
문자열의 길이를 리턴합니다. 아래는 간단히 만든 \TT{getline} 함수입니다:
\begin{verbatim}
ssize_t getline(char **lineptr, size_t *n, FILE *fp)
{
char *buf, *ptr;
size_t newsize, numbytes;
int cont;
int ch;
int pos;
if (lineptr == NULL || n == NULL || fp == NULL)
return -1;
buf = *lineptr;
if (buf == NULL || *n < MIN_LINE_SIZE) {
buf = realloc(*lineptr, DEFAULT_LINE_SIZE);
if (!buf)
return -1;
*lineptr = buf;
*n = DEFAULT_LINE_SIZE;
}
numbytes = *n;
ptr = buf;
cont = 1;
while (cont) {
/* fill buffer - leaving room for nul-terminator */
while (--numbytes > 0) {
if ((ch = getc(fp)) == EOF) {
cont = 0;
break;
}
else {
*ptr++ = ch;
if (ch == '\n') {
cont = 0;
break;
}
}
}
if (cont) {
/* Buffer is too small so reallocate a larger buffer. */
pos = ptr - buf;
newsize = (*n << 1);
buf = realloc(buf, newsize);
if (!buf) {
cont = 0;
break;
}
/* After reallocating, continue in new buffer */
*lineptr = buf;
*n = newsize;
ptr = buf + pos;
numbytes = newsize - pos;
}
}
/* if no input data, return failure */
if (ptr == buf)
return -1;
/* otherwise, nul-terminate and return number of bytes read */
*ptr = '\0';
return (ssize_t)(ptr - buf);
}
\end{verbatim}
\noindent 위 함수를 써서, 표준 입력(stdin)에서 한 줄씩 읽어서 출력하는 함수는
다음과 같이 만들 수 있습니다:
\begin{verbatim}
int main(void)
{
char *line = NULL;
ssize_t len;
size_t size;
while ((len = getline(&line, &size, stdin)) >= 0) {
printf("%s", line);
}
return 0;
}
\end{verbatim}
\R
\cite{glibc} \TT{stdio-common/getline.c}
\end{faq}
\section{Debugging} \label{sec:debugging}
이 단락에서는 debugging시 참고할 수 있는 여러가지 hint를 모았습니다.
\begin{faq} \label{faq:good-debugger}
\Q{21.3} % 21.3
좋은 debugger를 추천해 주세요.
\A
어떤 C 컴파일러가 가장 좋은 것인가?와 마찬가지로 해답이 나오기
힘든 질문입니다 ;-)
% TODO: GDB 이외의 debugger 소개,
% non-UNIX system에서 gdb를 쓸 수 있나? --- check
성능 좋고, 공개용인 GDB(GNU symbolic debugger)를 추천합니다.
GDB를 바로 command-line에서 사용하는 것은 가장 쉬운 방법이긴
하지만, \TT{vi}를 쓰지 않고 \TT{ed}를 써서 작업하는 격이 됩니다.
GNU Emacs에서는 GDB를 편하게 쓸 수 있는 mode를 제공하며,
DDD는 GDB를 써서 X window system에서 편하게 쓸 수 있는
interface를 제공합니다.
% TODO: specify the homepage of the DDD
% TODO: ???
\end{faq}
\begin{faq} \label{faq:segfault-after-main}
\Q{21.4} % 21.4
제가 만든 프로그램은 \TT{main()}을 끝낸 다음에
``segmentation fault''를 출력하고 끝나 버립니다. 왜 그럴까요?
\A
간단히 말해 C 언어로 작성한 프로그램은 \TT{main()}과 함께 시작하여
\TT{main()}이 끝나면 종료하게 됩니다. 컴파일러가 만들어 낸
object file을 executable 파일로 변환하는 과정에서,
command-line 인자인 \TT{argc}와 \TT{argv}를 설정하고,
\TT{main()}이 종료한 다음 OS에게 control를 넘겨주는 assembler
루틴이 들어가는 데, 프로그래머의 실수로 assembler routine으로
돌아가는 리턴 address가 저장되어 있는 stack을 건드리게 되면
(in technical term, write over the stack where the return
address is stored.) 그러한 상황이 발생하곤 합니다.
일단 \TT{main} 함수 내에서 선언된 local 배열이 있나 조사하고,
있다면, 그 배열의 범위를 벗어난 작업이 있었는지 확인하시기
바랍니다.
\end{faq}
\begin{faq}
\Q{21.5} % 21.5
이 글은 어떤 툴로 만든 것인가요?
\A
이 글은 \LaTeX와 한글 \LaTeX 패키지인 H\LaTeX을 써서 만든 것입니다.
\end{faq}
\section{Internationalization}
\begin{faq}
\Q{a}
Internationalization과 localization이라는 말이 무슨 뜻인가요?
\A
Internationalization, 또는 줄여서 I18N\footnote{18이란 숫자는,
`internationalization'이 열여덟 글자로 이루어졌기 때문에
붙었습니다}은 어떤 소프트웨어가 하나 이상의 문화,지역 또는 locale에
맞게 쓰일 수 있도록 준비하는 것을 의미합니다.
이와 반대로 Localization, 또는 줄여서 L10N은
소프트웨어가 한 문화, 지역 또는 locale에 맞게 쓰일 수 있도록
고치는 작업을 뜻합니다.
\R
\cite{cjkv} \page{17}
\end{faq}
\begin{faq}
\Q{b}
Multiple-byte, wide character가 정확히 뭔가요?
\A
byte 단위로 처리하는 encoding에서는 endianness(인디언)을 고려할
필요가 전혀 없습니다. 이러한 encoding에서 처리하는 문자를
multiple-byte character라고 합니다.
\begin{center}
\begin{tabular}{l|p{5cm}|l}
Encoding & Encoding Length & Locale \\ \hline
ASCII & one-byte & \EM{not applicable} \\
ISO-2022 & one- and two-byte & CJKV \\
EUC & one- through four-byte,
depending on locale & CJKV \\
GBK & one- and two-byte & China \\
Big Five & one- and two-byte & Taiwan \\
Big Five Plus & one- and two-byte & Taiwan \\
Shift-JIS & one- and two-byte & Japan \\
Johab & one- and two-byte & Korea \\
UHC & one- and two-byte & Korea \\
UTF-8 & one- through six-byte & \EM{not applicable} \\ \hline
\end{tabular}
\end{center}
이와 반대로, byte 단위로 처리할 수 없는 encoding에서는 endian 문제가
매우 중요한데, 이렇게 특별히 endian 처리가 필요한 문자를 wide
character라고 합니다.
\begin{center}
\begin{tabular}{l|l}
Encoding & Encoding Length \\ \hline
UCS-2 & 16-bit fixed \\
UCS-4 & 32-bit fixed \\
UTF-16 & 16-bit variable-length \\
Unicode Version 2.0 & \EM{Same as UTF-16} \\ \hline
\end{tabular}
\end{center}
\R
\cite{cjkv} \Page{25-26}
\end{faq}
%
% Local Variables:
% coding: utf-8
% fill-column: 78
% End:
%