Algorithm/C++

C++ String 출력 방식

cgy12306 2021. 3. 28. 20:39

알고리즘을 풀던 도중 의문을 갖게 하는 코드가 하나 나왔습니다.

 

어떤 것이냐면 바로 string 출력에 관한 코드였습니다.

 

#include<iostream>
#include<stdio.h>
#include<string>

using namespace std;

int main() {
	string s = "abcde";
	string s2 = "abc\0de";
	
	s[2] = 0;

	cout << "size : " << s.size() << " ";
	for (int i = 0; i < s.size(); i++) {
		printf("%x ", s[i]);
	}
	cout << s << "\n"; // abde

	cout << "size : " << s2.size() << " ";
	for (int i = 0; i < s2.size(); i++) {
		printf("%x ", s2[i]); // abc
	}
	cout << s2 << "\n";
}

 

코드를 실행해보면 다음과 같은 결과가 나옵니다.

 

이게 뭐가 문젠데??? 라고 생각이 드실 수 있지만 학교 수업에서나 대부분의 강의에서는 문자열 출력을 해줄 때 NULL까지만 출력을 해준다고 가르쳐줬을 겁니다. 

 

하지만 위의 코드에서 s라는 변수는 2번째 인덱스에 0인 NULL 값을 넣고 출력을 해봤더니 NULL을 무시(?) 혹은 출력을 해주고 다음 뒤에것도 출력하는 것을 볼 수 있습니다.

 

s2 변수는 초기에 할당할 때 문자열 사이에 NULL 값을 넣었는데 이건 또 abc 까지만 출력을 해줍니다.

 

분명 둘다 문자열 사이에 NULL 이 포함되어 있는데 하나는 전부 출력하고 하나는 도중에 잘려서 출력이 되는 겁니다.

 

그리고 size 또한 다르다는 것을 알 수 있습니다.

 

s는 5, s2는 6이라 생각했는데 3으로 출력되는 것입니다.

 

여기서 추론을 해볼 수 있습니다.

 

string 변수에 초기 값을 할당할 때에는 NULL까지 문자열을 인식하게 되어 도중에 문자열이 끊겨 크기에 영향이 있고, 초기 값이 할당 된 후에 문자열 사이에 NULL이 들어간다면 초기 할당됐을 때의 크기로 사이즈 값이 설정됩니다.

 

여기까지는 뇌피셜이었습니다.

 

직접 디버거를 이용해 내부 internal을 살펴봤습니다.

 

위의 화면은 cout 출력할 때 내부입니다.

 

call *ABS*+0x9ee50@plt 함수를 실행하고 나면 문자열 끝의 포인터를 반환하게 됩니다. (문자열 끝의 포인터는 출력하기전 allocator를 호출하여 끝 부분을 알아냅니다)

 

이렇게 포인터를 구한 뒤

 

 

_IO_file_overflow+223에서 sub rdx(문자열의 끝), rsi(문자열의 시작) 명령어를 통해 길이를 구하게 됩니다.

 

0x555555769268 - 0x55555769280 = 0x6

 

 

이렇게 구한 길이 값(0x6)은 rdx 레지스터에 들어가게 되고, rdx 레지스터는 write 함수를 호출할 때 인자로 사용됩니다.

 

write 함수는 NULL 값이 포함되어 있어도 길이만큼 출력해주기 때문에 abde라는 결과 값이 출력될 수 있었던 것입니다.

 

의미가 있을 수도 없을 수도 있는 삽질이긴 하지만 굳이 나서서 내부 internal을 볼 기회는 별로 없었으니 좋은 기회와 경험이었다 생각합니다.