【C++】std::string* への=を使った代入ができなかった件について

【解決方法】

=ではなく、memcpy関数を使う。
なぜこれで通るのか、理由はわからない。。

背景

データ構造を理解するために、vectorをc++で実装していた際に、遭遇。
テストを動かすとクラッシュしてしまった。
クラッシュの原因となっていたコードは、以下のdata[index]=xの文。

void insert(int index, T x){
    if ( is_full() == true ){ resize(); }
    for(int i=size(); i != index; i-- ){
        data[i] = data[i-1];
    }
    data[index] = x;
    size_++;
}

メンバ変数のdataは以下の、コンストラクタでメモリ確保している。

T *data;
vector(){
    size_ = 0;
    capacity_ = 1;
    malloc_data(1);
}
void malloc_data(int size){
    std::cout << size << " " << sizeof(T) * size << std::endl;
    data = (T*)malloc(sizeof(T)*size);
    if ( data == NULL ){ exit( EXIT_FAILURE ); }
}

テストコードで実行していたところは以下。

kf::vector<std::string> vec;                             
vec.insert(0, "b");

宣言時にコンストラクタが動いているはずなので、メモリは確保されていると思う。

問題の抜き出し

もっと簡単なコードで同じバグを出してみる。

int main() {
    string *data;
    data = (string*)malloc(sizeof(string));
    string tmp = "hoge";
    data[0] = tmp;
    cout << data[0] << endl;
}

上記コードをコンパイルし、実行してもバグは再現できず。

$ clang++  string_pointer_insert.cc
$ ./a.out
hoge

コードは同じだけど、同じバグが再現しないときや、実行するタイミングによって結果が違う時はメモリに関するバグのことが多い気がするので、コンパイルオプションを加えてみる。
すると、アドレスに関する警告が出てきた。

$ clang++ -I.. -Wall -Wextra -fsanitize=address string_pointer_insert.cc
$ ./a.out
AddressSanitizer:DEADLYSIGNAL
=================================================================
==19559==ERROR: AddressSanitizer: SEGV on unknown address 0x17d7d847d7d9d7d7 (pc 0x00010274d9c8 bp 0x00016dc85c80 sp 0x00016dc85440 T0)
==19559==The signal is caused by a READ memory access.
    #0 0x10274d9c8 in __asan::QuickCheckForUnpoisonedRegion(unsigned long, unsigned long)+0x1c (libclang_rt.asan_osx_dynamic.dylib:arm64+0x159c8) (BuildId: 5588b59c22213d7c98fe9ea04b6ac01132000000200000000100000000000b00)
    #1 0x1ba6b0444 in std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >& std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::__assign_no_alias<false>(char const*, unsigned long)+0x48 (libc++.1.dylib:arm64+0x12444) (BuildId: 3d1e6031901d3df19e9af85ff1c2e80332000000200000000100000000060c00)
    #2 0x61318001021789e8  (<unknown module>)
    #3 0x102259088 in start+0x204 (dyld:arm64+0x5088) (BuildId: 24d09537e51b350eb59e181c9d94d29132000000200000000100000000060c00)
    #4 0xcd767ffffffffffc  (<unknown module>)

==19559==Register values:
 x[0] = 0xbebebebebebebebe   x[1] = 0x0000000000000004   x[2] = 0x0000000000000004   x[3] = 0x00000001ba7707b0
 x[4] = 0x0000000104d02038   x[5] = 0x0000000000000001   x[6] = 0x000000016d48c000   x[7] = 0x000000016dc85ce0
 x[8] = 0x0000007000020000   x[9] = 0x17d7d7d7d7d7d7d7  x[10] = 0x000000002db90b9c  x[11] = 0x0000000000000280
x[12] = 0x000000016dc85b88  x[13] = 0x000000016dc85b80  x[14] = 0x0000000000007e01  x[15] = 0x0000000000000006
x[16] = 0x0000000102750878  x[17] = 0x000000021487bfe8  x[18] = 0x0000000000000000  x[19] = 0x0000000000000004
x[20] = 0x000000016dc85ce0  x[21] = 0xbebebebebebebebe  x[22] = 0x0000000000000000  x[23] = 0x0000000000000000
x[24] = 0x0000000000000000  x[25] = 0x0000000000000000  x[26] = 0x0000000000000000  x[27] = 0x0000000000000000
x[28] = 0x0000000000000000     fp = 0x000000016dc85c80     lr = 0x0000000102750a34     sp = 0x000000016dc85440
AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV (libclang_rt.asan_osx_dynamic.dylib:arm64+0x159c8) (BuildId: 5588b59c22213d7c98fe9ea04b6ac01132000000200000000100000000000b00) in __asan::QuickCheckForUnpoisonedRegion(unsigned long, unsigned long)+0x1c
==19559==ABORTING
zsh: abort      ./a.out

デバッガを使ってみると以下の表示が出てストップしたが意味がわからず。

$ lldb a.out
(lldb) run
0x1005099c8 <+28>: ldrsb  w9, [x8, x9]
0x1005099cc <+32>: and    w10, w0, #0x7
0x1005099d0 <+36>: cmp    w9, #0x0
0x1005099d4 <+40>: ccmp   w10, w9, #0x8, ne

std::stringで=を使用した時の挙動に関する警告だと予想して、=をしない方法を模索した。

いろいろと試行錯誤した結果、=を使う代わりにmemcpyを使用したコードなら警告が出なくなった。

int main() {
    string *data;
    data = (string*)malloc(sizeof(string));
    string tmp = "hoge";
    data[0] = tmp;
    memcpy( &data[0], &tmp[0], sizeof(string));
    cout << data[0] << endl;
}
clang++ -I.. -Wall -Wextra -fsanitize=address string_pointer_insert.cc
./a.out
hoge

感想

結果的にmemcpyを使用するとクラッシュが起きなくなったが、なぜ起きなくなったのか不明。
どなたかご存知の方、教えていただければ幸いです。。

念の為、問題を抽出したときのソースコードをgithubに上げておきます。
string_pointer_insert.cc

コメント

タイトルとURLをコピーしました