입력을 신뢰하면 안 된다. 현실 세계에서는 알고리즘 문제와 같이 제한에 맞춰서 입력이 들어오지 않는다. 이 말은 많이 들어보셨으리라 생각이 듭니다. c언어에서 많은 string 함수, 예를 들어서 strcpy, strcat 같은 것들이 이런 문제를 가지고 있다고 하는데, 무엇일까요?

 

 


 문자를 5개 저장할 수 있는 배열을 선언했습니다.

 

 문자열은 NULL 문자가 끝에 들어갑니다. 그러면, 길이가 4인 문자열까지는 들어갈 수 있습니다. 예를 들어 "cho"는 길이가 3인 문자열입니다. 그러므로, 들어갈 수 있습니다.

 

 

  그런데, 이런 경우라면 어떨까요? "chogahui를 넣는다. 길이가 8입니다.

 

 

 그런데, gets나, strcpy, strcat에 들어가는 정보는 string 배열의 시작 주소일 뿐입니다. 길이 값이 들어가지 않습니다. 그러면, 함수 내부에 들어갔을 때, 끝을 아는 방법이 있나요? 없습니다. 그렇기 때문에, 경계를 넘어가도 bound 처리를 하지 못합니다.

 

 

 stack에 이런 식으로 쌓인다고 해 봅시다. 스택의 윗 부분은 주소가 낮은 쪽입니다. ret은 함수가 종료되었을 때, 복귀할 주소를 저장하고 있습니다.

 

 

 이 주소를 임의의 주소로 덮어 씌웠다고 해 봅시다. 그러면 어떻게 될까요? 복귀 주소를 다른 주소로 덮어 씌운다면, 복귀할 때, 엉뚱한 주소로 가게 될 겁니다.

 

 

 이것이 버퍼 오버플로우입니다. 물론, 이렇게 엄청 쉽게 뚫리지는 않지만요. 한 가지 확실한 것은, 버퍼 크기보다 더 큰 입력이 들어왔는데, 경계 검사를 하지 않았다면 어떻게 동작할지 모르는 상황이 되어버립니다. gets 함수도 마찬가지입니다. 인자가 딱 하나밖에 없는데요. 문자열의 시작 주소만 받습니다. 내부적으로 경계 검사를 하지 않는다는 이야기입니다.

 

 이를 회피하는 가장 간단한 방법은, fgets와 같이, 입력 받을 길이를 지정해 주는 것입니다.

 

 

 

 2번째 인자를 주목하세요. 수를 넣었습니다. 100-1개까지 받겠다는 겁니다. 최소한의 경계 처리를 하겠다는 의미입니다. 저는 1줄의 길이가 100이 넘어가는 두 개의 문자열을 입력받았습니다. 어떻게 끊어질까요?

 

 

 처음에 ab~ 패턴으로 받고, 다음에 arpa~ 패턴으로 받았음을 알 수 있습니다. 만약에 100개의 길이까지 입력을 받아버렸다면, 1번째 문자열을 입력받을 때, ab~ab까지 입력을 받았어야 할 겁니다. 그런데, a까지 입력을 받아버렸습니다. 이는 str에 99개의 문자까지 넣어버렸다는 이야기가 됩니다. 개행이 있었다면, 개행을 받고 끝냅니다. 2번째로 str을 출력하고 나서, 3번째로 str을 출력할 때 개행이 2번 들어갔다는 것을 알 수 있어요.

 

 당연하게도, new line 문자가 문자열의 끝에 있는지 확인하고, new line을 제거해 주면 됩니다. 이렇게, 한 번에 받을 길이를 미리 정해놓고 조금씩 받는 방법이 있습니다. strcpy 또한 strncpy 같은 걸로 대체할 수는 있습니다.

 

 


 그런데, 이 방법 말고 또 다른 방법이 없을까요? 문자열을 문자들이 모인 배열이라고 생각합시다. 그러면, 우리는 길이를 모르는 문자열을 받는 것을 다음의 쿼리를 x번 연산하는 것으로 치환할 수 있습니다.

 

 맨 뒤에 어떠한 원소를 추가한다. c++에서는 vector의 push_back이 그러한 역할을 수행합니다. 이것은 어떨 때, 배열을 2배로 확장하나요? 예를 들어, capacity가 4였고, 현재 length가 3인 char형 배열이 있다고 생각해 봅시다. 이 때, 뒤에 'g'를 추가한다고 해 봅시다.

 

 

 추가 가능한가요? 네. 이 때 size는 3, capacity는 4인데, size가 capacity보다 작으니까, 뒤에 추가가 가능한 상황입니다.

 

 

 그러면 단순히 str[size]에 'g'를 넣고, size를 증가시키면 됩니다. 문제는 여기서 'a'라는 문자를 추가하는 연산이 들어온다면 어떨까요? 이 때에는, size와 capacity가 모두 4입니다. 더 이상 입력을 받을 수 없어요. 그렇기 때문에, expand 연산을 수행해야 하는데요.

 

 

 그럴려면, 기존 capacity였던 4의 2배 크기만큼을 할당합니다. 다음에, 새로운 배열에 기존에 있었던 내용을 복사합니다. 그러면, size는 4이고, capacity는 8이 됩니다.

 

 

 그러면, 뒤에 'a'를 추가할 수 있게 됩니다. C++의 String과 Java의 StringBuffer, StringBuilder를 보면, 공통적으로 보이는 함수가 3개가 있어요. size (혹은 length), capacity, expand. 어떤 자료구조를 쓰는지 간파할 수 있는 부분입니다.