cpp string 고찰 (sizeof string == 32)


(ㄴㅂㄷㄱㄷㄱ) #1

밑분이 하시는 data serialization 보다가 문득 이런 생각이 들었다.

struct AAA{
    char* name;
    int num1;
    int num2;
};

이렇게 생긴 struct 이 있다고 한다면
char* 는 포인터에 불과하니까

fwrite 로 적어봤자, 원문은 안 적히고 글자가 적힌 포인터만 저장되는 거 아닌가 (즉 deep copy 가 아닌 shallow 에 불과)
해서 직접 돌려보고 싶어졌다.

그런데 cpp 에서 char* 보다는 되도록이면 string 을 사용하라길래 그걸로 바꿔줫다.

그런데 신기한 일이 벌어졌다.

일단 sizeof AAA 는 40 이다.
char* + int + int 하면 16이 나올줄 알았는데
sizeof string 이 32 라서 40이 나왔다.

그래서 결과물인 data1.bin도 40 바이트다

wc data1.bin

으로 bash 에서 확인해볼 수 있다.

data1.bin 의 내용물을 확인해 보았다.

cat data1.bin
혹은
xxd data1.bin

그런데 신기하게도 string 의 내용이 저장되어있는 것 아닌가?

00000000: 40a4 e0e1 fe7f 0000 0700 0000 0000 0000  @...............
00000010: 4d79 204e 616d 6500 0000 0000 0000 0000  My Name.........
00000020: 0a00 0000 1400 0000

이렇게…

분명 string 은 malloc 으로 힙에 char* 선언하고 그 포인터과 길이를 관리하는 방식인 줄 알았더니,
string 클래스 자체에 My Name 이라는 문자열이 저장되어 있어 당황했다…

My Name -> Your Name 으로 바꿔봐도 마찬가지다.

sizeof string 하면 32 가 나왔다.

아하, 짧은 넘들은 임의로 저장해버리고, 긴 놈들만 포인터 형식으로 관리하는구나

실험을 해보았다.

#include <iostream>
using namespace std;
#include <cstring>

void print_string(string& a){
    auto *p = (unsigned char *)&a;
    for(size_t i = 0; i < sizeof a; ++i){
        printf("%p: %x", p, *p);
        if(*p >= 32 && *p < 127){
            printf("\t:%c", *p);
        }
        printf("\n");
        p++;
    }
}

int main(){
    string a = "hello world!";
    cout << a << endl;
    std::cout << sizeof a << std::endl;
    print_string(a);

    a = "wow!";
    cout << a << endl;
    std::cout << sizeof a << std::endl;
    print_string(a);

    a = "helloworldarstarstarstarst";
    cout << a << endl;
    std::cout << sizeof a << std::endl;
    print_string(a);

    printf("experiment\n");

    char *p = *(char **)&a;
    printf("%p\n", p);
    printf("%s\n", p);
    printf("%x\n", strlen(p));

    return 0;
}
0x7ffee63d7390: a0
0x7ffee63d7391: 73      :s
0x7ffee63d7392: 3d      :=
0x7ffee63d7393: e6
0x7ffee63d7394: fe
0x7ffee63d7395: 7f
0x7ffee63d7396: 0
0x7ffee63d7397: 0
0x7ffee63d7398: c
0x7ffee63d7399: 0
0x7ffee63d739a: 0
0x7ffee63d739b: 0
0x7ffee63d739c: 0
0x7ffee63d739d: 0
0x7ffee63d739e: 0
0x7ffee63d739f: 0
0x7ffee63d73a0: 68      :h
0x7ffee63d73a1: 65      :e
0x7ffee63d73a2: 6c      :l
0x7ffee63d73a3: 6c      :l
0x7ffee63d73a4: 6f      :o
0x7ffee63d73a5: 20      :
0x7ffee63d73a6: 77      :w
0x7ffee63d73a7: 6f      :o
0x7ffee63d73a8: 72      :r
0x7ffee63d73a9: 6c      :l
0x7ffee63d73aa: 64      :d
0x7ffee63d73ab: 21      :!
0x7ffee63d73ac: 0
0x7ffee63d73ad: 0
0x7ffee63d73ae: 0
0x7ffee63d73af: 0
wow!
32
0x7ffee63d7390: a0
0x7ffee63d7391: 73      :s
0x7ffee63d7392: 3d      :=
0x7ffee63d7393: e6
0x7ffee63d7394: fe
0x7ffee63d7395: 7f
0x7ffee63d7396: 0
0x7ffee63d7397: 0
0x7ffee63d7398: 4
0x7ffee63d7399: 0
0x7ffee63d739a: 0
0x7ffee63d739b: 0
0x7ffee63d739c: 0
0x7ffee63d739d: 0
0x7ffee63d739e: 0
0x7ffee63d739f: 0
0x7ffee63d73a0: 77      :w
0x7ffee63d73a1: 6f      :o
0x7ffee63d73a2: 77      :w
0x7ffee63d73a3: 21      :!
0x7ffee63d73a4: 0
0x7ffee63d73a5: 20      :
0x7ffee63d73a6: 77      :w
0x7ffee63d73a7: 6f      :o
0x7ffee63d73a8: 72      :r
0x7ffee63d73a9: 6c      :l
0x7ffee63d73aa: 64      :d
0x7ffee63d73ab: 21      :!
0x7ffee63d73ac: 0
0x7ffee63d73ad: 0
0x7ffee63d73ae: 0
0x7ffee63d73af: 0
helloworldarstarstarstarst
32
0x7ffee63d7390: 70      :p
0x7ffee63d7391: 28      :(
0x7ffee63d7392: 40      :@
0x7ffee63d7393: 29      :)
0x7ffee63d7394: e3
0x7ffee63d7395: 7f
0x7ffee63d7396: 0
0x7ffee63d7397: 0
0x7ffee63d7398: 1a
0x7ffee63d7399: 0
0x7ffee63d739a: 0
0x7ffee63d739b: 0
0x7ffee63d739c: 0
0x7ffee63d739d: 0
0x7ffee63d739e: 0
0x7ffee63d739f: 0
0x7ffee63d73a0: 1e
0x7ffee63d73a1: 0
0x7ffee63d73a2: 0
0x7ffee63d73a3: 0
0x7ffee63d73a4: 0
0x7ffee63d73a5: 0
0x7ffee63d73a6: 0
0x7ffee63d73a7: 0
0x7ffee63d73a8: 72      :r
0x7ffee63d73a9: 6c      :l
0x7ffee63d73aa: 64      :d
0x7ffee63d73ab: 21      :!
0x7ffee63d73ac: 0
0x7ffee63d73ad: 0
0x7ffee63d73ae: 0
0x7ffee63d73af: 0
experiment
0x7fe329402870
helloworldarstarstarstarst

hello world! -> wow! -> helloworldarstarstarstarst
문자열로 차례차례 바꿔보았다.

hello world! 같은 짧은 문자열은 string 클래스 내에서 자체적으로 저장한다.
일단 처음 8바이트는 문자열의 주소를 가르키고 (이 경우 자체 문자열)
다음 8바이트는 문자열의 길이,
그 다음부터 자체 문자열이 시작한다. 총 16바이트까지 저장 가능한가보다.

wow! 로 바뀌자 hello 부분만 덮어씌워지고 나머지는 고대로 world! 로 남아있는 것을 알 수 있다.

그 다음 자체 문자열이 감당 안 될 만큼 긴 문자열을 저장하니 (예시: helloworldarstarstarstarst)
처음 8바이트가 자체 문자열의 주소를 가르키던 것이, 다른 주솟값을 가지기 시작하고

이를

char *p = *(char **)&a;
printf("%s\n", p); 

를 이용해 출력해보았더니
역시
helloworldarstarstarstarst
를 출력해준다.

덕분에 스트링이 대략 어떻게 작동하는지 알게 된 것 같다.

첫 8바이트는 문자열의 주소
다음 8바이트는 string 의 길이
나머지 16바이트는 자체 문자열

물론 gcc 만의 implementation 일 수도 있고, 꼭 이렇게 만들어야 한다는 standard 보장은 모르겟다.


(바보털) #2

이거 라이브러리마다 구현 달라서 sizeof(std::srting)도 제각각인 것으로 알고 있습니다.
이펙티브 STL에서 봄…