-
[운영체제 만들기] 문자열 - 라이브러리프로젝트/운영체제 만들기 2023. 5. 18. 19:27
글의 참고
https://en.cppreference.com/w/
글의 전제
- 내가 글을 쓰다가 궁금한 점은 파란색 볼드체로 표현했다. 나도 모르기 때문에 나중에 알아봐야 할 내용이라는 뜻이다.
- 밑줄로 작성된 글은 좀 더 긴 설명이 필요해서 친 것이다. 그러므로, 밑 줄 처친 글이 이해가 안간다면 링크를 따라서 관련 내용을 공부하자.
- `글의 참조`에서 빨간색 볼드체로 체크된 링크는 이 글을 작성하면 가장 많이 참조한 링크다.
- `운영체제 만들기` 파트에서 퍼온 모든 참조 글들과 그림은 반드시 `이 글과 그림을 소스 코드로 어떻게 구현을 해야할까` 라는 생각으로 정말 심도있게 잠시 멈춰서 생각해봐야 실력이 발전한다.
글의 내용
- 문자열 길이
: 문자열 관련 처리는 대개 NULL문자를 기반으로 크기를 판단하는데, 사용자가 NULL 문자를 보내지 않을 경우 그때부터 문제가 발생한다. 아래와 같이 strlen()을 구현할 경우, 전달받은 str이 NULL로 끝나지 않는다면 어떻게 해야할까? 저 str[n]이 0을 만나지 않는 이상 n의 값은 무한대로 커질 수 있다. 그러면 문제는 길이를 계산하는 변수(여기서는 n)의 최대값을 상한으로 잡고, 이 값보다 더 커지면 에러 메세지를 출력하고 에러를 반환하도록 구현해야 할 듯 싶다.
int strlen(const char* str) { int n = 0; if(!str) return 0; while(str[n]) n++; return n; }
: INT 데이터 타입의 최대값까지 고려한 strlen() 다음과 같다. 그러나 실제는 CPU가 지원하는 워드타입으로 바꿔야 한다. 현재 동작하고 있는 CPU가 지원하는 최대 데이터 타입으로 고려해야 하지 않나 싶다. 그래서 리눅스 같은 경우는 size_t 같은 아키텍처에 따라 데이터 타입이 유동적으로 변하는 데이터 타입을 사용하는 데, YohdaOS는 이런 부분은 나중에 시간이 남을 때, 최종적으로 고려할 생각이다.
int strlen(const char* str) { int n = 0, i = 0; if(!str) return 0; for(i=0 ; str[n]&&(i<=INT_MAX); i++) n++; return (n != INT_MAX) ? n : 0; }
: 나중에 strcmp()를 보면 알겠지만, strlen()는 NULL이 들어오면 0을 반환하는 것이 동작상 자연스럽다.
- 문자열 복사
: 그리고 strncpy() 함수를 보자. 아래 함수에서 중요한 문제가 있다. 바로 에러 체크가 문제다. dst가 문자열을 복사할 때, 대게 문자열 복사전에 목적지 문자열을 memset()을 통해 0으로 초기화하는 경우가 있다. 그런 경우 dst가 0으로 되어 이 함수는 NULL을 반환한다. 그러므로, dst에 대해 NULL 체크가 힘들고 dst의 사이즈 또한 알기가 힘들다. 전적으로 cnt에만 의존할 수 밖에 없다. 그래도 약간 코드를 방어해보자면,cnt와 src의 길이를 비교해서 dst에 쓰는 방법밖에 없다. 근데, 만약 dst가 메모리를 할당받지 않은 변수라면 에러를 뿜게될 것이다. 이걸 사전에 알 수 있는 방법이 있는지 모르겠다...
: 사실 가장 중요한 건 dst의 길이다. 만약 cnt의 값이 dst의 길이보다 길어지면 overflow가 발생한다. 그래서 dst의 길이가 cnt보다 작게 설정해서 처리를 해야한다. 그런데 dst를 알 수 있는 방법이 없으니 이러한 문제가 발생하게 된다.
char *strncpy(char *dst, const char *src, int cnt) { int i = 0 , len = 0; if(!dst || !src) return NULL; for(i=0 ; i<len ; i++) { dst[i] = src[i]; } return dst; }
: 내가 생각하기에 가장 이상적인 방법은 외부에서 주는 정보는 믿을 수 없으니, 내부에서 처리해야 한다고 생각한다. 즉, src의 길이만큼 dst에 새로 할당해서 값을 복사한 후 반환해주는 것이다. 그런데 src의 정보는 믿을 수 있을까? src의 길이를 strlen()으로 구할텐데, 이 정보는 과연 믿을 수 있을까? strncpy() 함수가 나온 배경도, strcpy()의 src를 이상한 값으로 줘서 strncpy()가 나온 것으로 알고 있는데, 여기에 strncpy()의 n의 값을 엄청크게 조작하면 dst는 반드시 overflow가 발생할 수 밖에 없다. 안전한 방법은 존재할까?
: 그나마 해 볼 수 있는 방법으로 src 길이와 cnt 값을 비교해서 더 작은 값을 길이로 쓰기로 일단 결정했다.
char *strncpy(char *dst, const char *src, int cnt) { int i = 0 , len = 0; if(!dst || !src) return NULL; if(cnt < 1) return NULL; len = min(strlen(src), cnt); for(i=0 ; i<len ; i++) { dst[i] = src[i]; } return dst; }
- 문자열 비교
: 문자열 비교는 결론부터 말하면 쓰지 않는 것이 좋다고 본다. 왜냐면, NULL 문자까지 비교에 대상으로 보기 때문에 에러 처리가 불가능하다고 보 수 있다. 사실 나는 이 함수는 개인적으로 잘못만들어 졌다고 생각된다. 즉, 같다, 다르다 정도만 판단을 하고 에러 반환값을 만들어 놔야 한다고 생각이 드는데, 이 함수에는 에러 반환값을 알 수 있는 플래그가 없다. 그러므로, 이왕이면 strcmp() 보다는 다른 비교 함수를 쓰느 것이 훨씬 좋다고 생각된다.
: 문자열 비교는 가장 난해하다. 반환값이 정수인데, 양수, 0, 음수 모두 의미를 지니기 때문에 에러가 났다는 것을 반환하기가 상당히 난해하다. 일단 strcmp() 에서는 NULL이 들어와도 비교는 가능하다. NULL 문자도 아스키 코드값으로 0이기 때문이다.
- 문자열 결합
: 문자열 결합 함수 strcat()은 굉장히 위험한 함수다. 이 함수는 2개의 char *를 인자로 받는데, 앞쪽 문자열에 뒤쪽 문자열을 합치는 역할을 한다. 그리고 반환값은 합친 문자열을 반환한다. 근데 그 주소는 앞쪽 문자열의 시작 주소가 된다. 이걸 통해 대략저인 strcat()의 구현 방법을 추론해보면, 말 그대로 앞 쪽 문자열에 NULL문자부터 뒤쪽 문자열을 붙힌다고 볼 수 있다. 그런데 당연히 여기에 주의 사항이 있다. 앞쪽 문자열은 뒤쪽 문자열 길이를 포함할 수 있을 정도의 충분한 사이즈를 가진 문자열이여야 한다는 것이다. 그렇지 않으면 당연히 페이즈 폴트 에러가 발생할 것이다. 문제는 아래와 같이 이상한 코드들도 일단 에러를 발생시키지 않을 수 있다는 것이다.
#include <stdio.h> #include <string.h> int main() { char *s = NULL; char *str = malloc(2); strcpy(str, "wef"); s = strcat(str, "efefefvds"); printf("%s\n", s); printf("0x%x\n", str); // 0x9b4b72a0 printf("0x%x\n", s); // 0x9b4b72a0 return 0; }
: 말도 안되는 오류 덩어리 코드다. 2B 메모리를 할당받고, 3바이트 문자열을 카피한 뒤, 9B를 덭붙힌다. 이게 가능하려면, str 변수는 최소 NULL 문자를 포함해서 13B를 할당받아야 하지만 이 코드는 에러를 발생시키지 않을 뿐더러 심지어 출력까지도 정상적이다. 꼭 사용해야 하는 경우를 제외하면 쓰지 말자.
'프로젝트 > 운영체제 만들기' 카테고리의 다른 글
[운영체제 만들기] Exception (0) 2023.05.25 [운영체제 만들기] 에러 사항 (0) 2023.05.24 [운영체제 만들기] 코딩 컨벤션 (0) 2023.05.16 [운영체제 만들기] VBR (0) 2023.03.26 [운영체제 만들기] MBR (0) 2023.03.26