위의 코드를 설명하겠습니다.
error()의 첫번째 인자는 0이 아닌 경우,
라이브러리 함수 exit()로 전달됩니다. 따라서
<stdlib.h>에 정의된 0이 아닌 값을 가지는
EXIT_FAILURE 상수를 써서 error()가 메시지를
출력하고 프로그램을 종료하도록 합니다
error()에 전달된 두번째 인자인,
errno는 <errno.h>에 정의된 전역 변수로서
몇몇 라이브러리 함수들이 보고한 에러의 종류를 나타내는
변수입니다. 이 값은 error()가 strerror 함수를
써서 에러를 나타내는 문자열로 바꾼 다음 출력합니다.
시스템과 라이브러리 함수가 보고한 에러가 아니라면
error()의 두번째 인자로 0을 주면 됩니다.
error()의 세번째 인자부터는 printf()가 쓰는
형식 그대로 쓰시면 됩니다.
물론 위의 예제가 완벽하지는 않습니다. 여러분은 error()
함수를 쓰기 위해, 앞에서 제시한 error() 정의와,
이 함수가 쓰는 전역 변수들을 적당하게 추가해야 합니다.
에러 함수의 선언과 va_list, va_start(), va_end()에
관한 것은 15 절을 참고하기 바랍니다.
User input을 string으로 받은 다음, atoi 함수를 써서
수치로 바꾸어 작업 했습니다. 그런데 가끔 이상한 문제가
발생합니다. 왜 그럴까요?
Answer
atoi는 주어진 문자열을 10진수로 해석해서
int로 바꾸어 주지만,
에러는 처리해 주지 않습니다.
다시 말해서 입력 문자열을 10진수로 처리할 수 없을 때에는
대개 0을 리턴합니다.
따라서 다음과 같은 함수를 만들어 쓰는 것이 좋습니다:
#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;
}
위 함수는 두 개의 인자를 받습니다. 첫 인자는 변환한 long int를
저장할
포인터를 받고, 두 번째 인자는 변환할 문자열을 받습니다.
리턴 값은 성공적으로 변환한 경우에는 0을, 그렇지 않은 경우에는 을
리턴합니다 - 입력에 예상할 수 없는 문자값이 오거나, 입력이 아예
없거나(문자열이 '\0'인 경우), 변환한 값이 long int에
담을 수 없을 만큼 크거나 작은 경우, 에러로 처리합니다.
실제 사용한 예는 다음과 같습니다:
void
foo(void)
{
char instr[80];
int i;
/* instr이 user input을 가지고 있다고 가정 */
if (convint(&i, instr) < 0) {
/* error: invalid user input */
}
/* ... */
}
주의할 것은 이 convint 함수가 atoi와 똑같이 동작하는 게
아니라는 것입니다 -- 단순히 리턴값과 인자만의 문제가 아닙니다.
atoi와 convint의 다른 점은 크게 다음과 같습니다:
atoi는 에러를 검사하지 않습니다.
따라서 입력이 "12xyz"인 경우에 12를 리턴합니다.
convint는 입력 문자열이 완전한 경우에만 처리하므로,
입력이 "12xyz"인 경우에는 int 변환을 수행하지 않습니다
-- 즉 에러로 처리합니다.
atoi의 리턴 타입이 int인 반면, convint의 경우
long int입니다.
atoi는 10진수만 처리할 수 있는 반면, convint는
strtol의 세번째 인자로 0을 주었기 때문에, 8진수나 16진수도
처리할 수 있습니다; 16진수의 경우 문자열 앞에 "0x"를 붙여야
하며, 8진수의 경우에 문자열 앞에 "0"을 붙여야 합니다.
Q 21.6
주어진 파일(FILE *)에서 한 줄씩 읽으려고 합니다. 질문 12.23에
따르면 gets를 쓰는 것은 좋지 않다고 했는데, 그럼 어떻게 해야 하나요?
fgets을 쓰면, 버퍼의 크기를 지정해야 하기 때문에, 파일의 한 줄이
지정한 버퍼보다 긴 경우, 잘려나가 버립니다.
Answer
이 문제를 해결하기 위한 표준 함수는 존재하지 않습니다. 아주 긴 줄도
읽기 위해서는, 버퍼의 크기를 미리 지정하지 않고, 필요할 경우 늘려나가는
방식을 써야 합니다. 표준 함수는 아니지만, [GLIBC]는 이러한 일을
처리하기 위해 getline 함수를 제공합니다. 좀 더 향상된 기능이 필요하다면
GNU readline 패키지를 써 보기 바랍니다:
getline 함수는, 미리 malloc 또는 realloc을 써서
할당한 버퍼와 그 크기를 입력받아서, 한 줄을 읽어 버퍼에 저장해 줍니다.
이 때 \n 문자도 포함합니다.
만약 버퍼의 크기가 부족하면 realloc을 불러서, 버퍼의 크기를 자동으로
키워 줍니다. EOF를 만나거나 에러가 발생하면 -1을 리턴하고, 성공한 경우에는
문자열의 길이를 리턴합니다. 아래는 간단히 만든 getline 함수입니다:
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);
}
위 함수를 써서, 표준 입력(stdin)에서 한 줄씩 읽어서 출력하는 함수는
다음과 같이 만들 수 있습니다:
성능 좋고, 공개용인 GDB(GNU symbolic debugger)를 추천합니다.
GDB를 바로 command-line에서 사용하는 것은 가장 쉬운 방법이긴
하지만, vi를 쓰지 않고 ed를 써서 작업하는 격이 됩니다.
GNU Emacs에서는 GDB를 편하게 쓸 수 있는 mode를 제공하며,
DDD는 GDB를 써서 X window system에서 편하게 쓸 수 있는
interface를 제공합니다.
Q 21.4
제가 만든 프로그램은 main()을 끝낸 다음에
``segmentation fault''를 출력하고 끝나 버립니다. 왜 그럴까요?
Answer
간단히 말해 C 언어로 작성한 프로그램은 main()과 함께 시작하여
main()이 끝나면 종료하게 됩니다. 컴파일러가 만들어 낸
object file을 executable 파일로 변환하는 과정에서,
command-line 인자인 argc와 argv를 설정하고,
main()이 종료한 다음 OS에게 control를 넘겨주는 assembler
루틴이 들어가는 데, 프로그래머의 실수로 assembler routine으로
돌아가는 리턴 address가 저장되어 있는 stack을 건드리게 되면
(in technical term, write over the stack where the return
address is stored.) 그러한 상황이 발생하곤 합니다.
일단 main 함수 내에서 선언된 local 배열이 있나 조사하고,
있다면, 그 배열의 범위를 벗어난 작업이 있었는지 확인하시기
바랍니다.
Internationalization, 또는 줄여서 I18N21.2은 어떤 소프트웨어가 하나 이상의 문화,지역 또는 locale에
맞게 쓰일 수 있도록 준비하는 것을 의미합니다.
이와 반대로 Localization, 또는 줄여서 L10N은
소프트웨어가 한 문화, 지역 또는 locale에 맞게 쓰일 수 있도록
고치는 작업을 뜻합니다.
Next: 22. ISO C Standard Consideration Up: C Programming FAQs Previous: 20. Miscellaneous
Subsections
21. Extensions
이 chapter에서는 역자가 추가한 질문들을 정리해 놓았습니다.
21.1 miscellaneous code
일단 위와 같이 error() 함수를 만들었으면 여러분의 코드에서 다음과 같이 쓸 수 있습니다.
위의 코드를 설명하겠습니다. error()의 첫번째 인자는 0이 아닌 경우, 라이브러리 함수 exit()로 전달됩니다. 따라서
<stdlib.h>
에 정의된 0이 아닌 값을 가지는 EXIT_FAILURE 상수를 써서 error()가 메시지를 출력하고 프로그램을 종료하도록 합니다error()에 전달된 두번째 인자인, errno는
<errno.h>
에 정의된 전역 변수로서 몇몇 라이브러리 함수들이 보고한 에러의 종류를 나타내는 변수입니다. 이 값은 error()가 strerror 함수를 써서 에러를 나타내는 문자열로 바꾼 다음 출력합니다. 시스템과 라이브러리 함수가 보고한 에러가 아니라면 error()의 두번째 인자로 0을 주면 됩니다.error()의 세번째 인자부터는 printf()가 쓰는 형식 그대로 쓰시면 됩니다.
물론 위의 예제가 완벽하지는 않습니다. 여러분은 error() 함수를 쓰기 위해, 앞에서 제시한 error() 정의와, 이 함수가 쓰는 전역 변수들을 적당하게 추가해야 합니다.
에러 함수의 선언과 va_list, va_start(), va_end()에 관한 것은 15 절을 참고하기 바랍니다.
따라서 다음과 같은 함수를 만들어 쓰는 것이 좋습니다:
위 함수는 두 개의 인자를 받습니다. 첫 인자는 변환한 long int를 저장할 포인터를 받고, 두 번째 인자는 변환할 문자열을 받습니다. 리턴 값은 성공적으로 변환한 경우에는 0을, 그렇지 않은 경우에는
을
리턴합니다 - 입력에 예상할 수 없는 문자값이 오거나, 입력이 아예
없거나(문자열이
'\0'
인 경우), 변환한 값이 long int에 담을 수 없을 만큼 크거나 작은 경우, 에러로 처리합니다.실제 사용한 예는 다음과 같습니다:
주의할 것은 이 convint 함수가 atoi와 똑같이 동작하는 게 아니라는 것입니다 -- 단순히 리턴값과 인자만의 문제가 아닙니다. atoi와 convint의 다른 점은 크게 다음과 같습니다:
"12xyz"
인 경우에 12를 리턴합니다. convint는 입력 문자열이 완전한 경우에만 처리하므로, 입력이"12xyz"
인 경우에는 int 변환을 수행하지 않습니다 -- 즉 에러로 처리합니다."0x"
를 붙여야 하며, 8진수의 경우에 문자열 앞에"0"
을 붙여야 합니다.getline 함수는, 미리 malloc 또는 realloc을 써서 할당한 버퍼와 그 크기를 입력받아서, 한 줄을 읽어 버퍼에 저장해 줍니다. 이 때
\n
문자도 포함합니다. 만약 버퍼의 크기가 부족하면 realloc을 불러서, 버퍼의 크기를 자동으로 키워 줍니다. EOF를 만나거나 에러가 발생하면 -1을 리턴하고, 성공한 경우에는 문자열의 길이를 리턴합니다. 아래는 간단히 만든 getline 함수입니다:위 함수를 써서, 표준 입력(stdin)에서 한 줄씩 읽어서 출력하는 함수는 다음과 같이 만들 수 있습니다:
21.2 Debugging
성능 좋고, 공개용인 GDB(GNU symbolic debugger)를 추천합니다. GDB를 바로 command-line에서 사용하는 것은 가장 쉬운 방법이긴 하지만, vi를 쓰지 않고 ed를 써서 작업하는 격이 됩니다.
GNU Emacs에서는 GDB를 편하게 쓸 수 있는 mode를 제공하며, DDD는 GDB를 써서 X window system에서 편하게 쓸 수 있는 interface를 제공합니다.
일단 main 함수 내에서 선언된 local 배열이 있나 조사하고, 있다면, 그 배열의 범위를 벗어난 작업이 있었는지 확인하시기 바랍니다.
21.3 Internationalization
Next: 22. ISO C Standard Consideration Up: C Programming FAQs Previous: 20. Miscellaneous
All rights reserved. Copyright © 2004-2006 Seong-Kook Shin (신성국)
Return to my homepage
2006-08-30
Recent Posts
Archive Posts
Tags