Angrave System Programming/Learning C
Text Input And Output(2)
몰랑&봉봉
2019. 1. 8. 17:04
Parsing Input
How do I parse numbers from strings?
문자열을 정수로 파싱하려면 strtol(long int) 또는 strtoll(long long int)를 사용합니다.
두 함수의 원형은 다음과 같습니다.
long int strtol(const char *nptr, char **endptr, int base);
long long int strtoll(const char *nptr, char **endptr, int base);
두 함수 모두 첫번째 인자로는 변형할 문자열, 두번째 인자는 숫자가 아닌 문자열의 시작 위치를 구해줍니다. 만약 NULL을 넘겨준다면 따로 구하지 않습니다. 세 번째 인자는 문자열의 진수를 입력해 줍니다.(2~32)
int main(){ const char *nptr = "1A2436"; char* endptr; long int result = strtol(nptr, &endptr, 16); return 0; }
위의 예에서는 nptr이 모두 16진수 정수로 변환될 수 있으므로 endptr에는 null character만 존재합니다.
result에는 0x1A2436의 값이 저장됩니다.
만약 strtol에서 16진수가 아니라 10진수 정수로 변환했다면
endptr에는 "A2436\0"의 시작지점을 저장하고, result는 1이 됩니다.(10진수에는 A가 없기 때문에)
strtol 함수는 error code를 반환하지 않기 때문에 error handling에 주의해야 합니다.
만약 string에 적절한 숫자값이 존재하지 않는다면 error code를 반환하는 것이 아닌 0을 반환합니다.
이러한 반환은 "0"이라는 문자열을 적절하게 변환하여 반환한 값과 잘못된 값을 입력해 0을 반환한 경우를 구별할 수 없게 합니다.
strtol의 man page를 읽어보면 long int의 boundary를 벗어난 경우와 유효한 0의 반환값을 구별하는 코드를 볼수 있습니다.
조금 더 안전한 방법은 sscanf를 사용해 return value를 체크하는 것입니다.
How do I parse input string scanf into parameters?
scanf(fscanf,sscanf)를 이용하면 기본 입력 stream(임의의 file stream, C string)으로부터 입력받은 값을 각각의 데이터로 저장할 수 있습니다.
이 함수의 return value를 체크해서 몇개가 파싱되었는지 알 수 있습니다.
scanf는 유효한 포인터를 필요로 합니다. 다음의 예는 정확하지 않은 pointer값을 넘겨준 에러 코드입니다.
int *data = (int *) malloc(sizeof(int)); char *line = "v 10"; char type; // Good practice: Check scanf parsed the line and read two values: int ok = 2 == sscanf(line, "%c %d", &type, &data); // pointer error
위 코드에서 character value를 char에, integer value를 malloc된 메모리에 쓰려고 합니다. 하지만 포인터가 가리키는 값이 아닌 데이터의 포인터의 주소값을 변수로 전달했습니다. 이런 경우 sscanf는 pointer 주소 그 자체를 바꿔버립니다. 즉, 이 경우 data는 address 10을 가리키게 됩니다(data의 %p를 찍으면 0xa). 즉 주소값 안에 데이터가 10이 아니라 주소 자체를 10으로 바꾸는 것입니다.
이런 코드를 짜면 data의 주소가 malloc을 해준 주소가 아니므로 free시 segmentation fault가 발생합니다.
How do I stop scanf from causing a buffer overflow?
다음 코드는 scanf가 null character를 포함해 10개 이상의 문자를 읽지 않는다고 가정합니다.
char buffer[10]; scanf("%s",buffer);
이런 경우 다음과 같이 optional integer로 null character를 제외한 문자를 얼마나 받을지 정할 수 있습니다.
char buffer[10]; scanf("%9s", buffer); // reads up to 9 charactes from input (leave room for the 10th byte to be the terminating byte)
위오 같은 코드는 buffer에 9의 문자만 받아 마지막 바이트에 null character를 넣을 여유를 만들어주고 있습니다.
Why is gets dangerous? What should I use instead?
char buf[10]; gets(buf); // Remember the array name means the first byte of the array
위의 코드는 buffer overflow에 취약하게 될 것입니다. 위와 같은 코드는 input이 null character를 포함해 10개의 문자를 넘지 않는다고 가정하고 있습니다.
gets함수는 C99 standard에서 사용되지 않았으며 C11에서부터는 삭제되었습니다. gets 대신 fgets나 getline을 이용해야 합니다.
두 함수의 원형은 다음과 같습니다.
char *fgets (char *str, int num, FILE *stream); ssize_t getline(char **lineptr, size_t *n, FILE *stream);
다음의 예는 fgets를 이용해 한 줄을 읽어오는 안전한 방법입니다. 다음과 같은 경우 9자 이상의 문자는 잘릴 것입니다.
char buffer[10]; char *result = fgets(buffer, sizeof(buffer), stdin);
error가 발생했거나 파일에 끝에 도달하면 NULL이 반환됩니다. gets와 달리 fgets는 buffer에 null string 대신 new line을 복사합니다.
new line 대신 null character를 복사하고 싶다면 fgets 이후에 아래와 같은 코드를 작성해야 합니다.
if (!result) { return; /* no data - don't read the buffer contents */} int i = strlen(buffer) - 1; if (buffer[i] == '\n') buffer[i] = '\0';
How do I use getline?
getline의 장점은 heap에 존재하는 buffer를 자동적으로 충분한 크기로 할당해 준다는 것입니다.
// ssize_t getline(char **lineptr, size_t *n, FILE *stream); /* set buffer and size to 0; they will be changed by getline */ char *buffer = NULL; size_t size = 0; ssize_t chars = getline(&buffer, &size, stdin); // Discard newline character if it is present, if (chars > 0 && buffer[chars-1] == '\n') buffer[chars-1] = '\0'; // Read another line. // The existing buffer will be re-used, or, if necessary, // It will be `free`'d and a new larger buffer will `malloc`'d chars = getline(&buffer, &size, stdin); // Later... don't forget to free the buffer! free(buffer);
이 경우 buffer는 getline에서 malloc이 된 것이므로 free 해주는 것을 잊지 맙시다!