C++ String 출력 방식
알고리즘을 풀던 도중 의문을 갖게 하는 코드가 하나 나왔습니다.
어떤 것이냐면 바로 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을 볼 기회는 별로 없었으니 좋은 기회와 경험이었다 생각합니다.