C++標準ライブラリの文字列型 std::string
*1 を、C言語インタフェースへの文字列取得バッファとして使う方法についてメモ。本記事の内容はStack Overflowで見つけた質問と回答に基づく。
2016-03-27追記:C++17(C++1z)対応版はこちら→文字列取得バッファとしてのstd::string リターンズ - yohhoyの日記
2013-08-30追記:C++03以前でも事実上「std::string
内の文字列バッファは連続したメモリ領域」といえる?C++ Standard Library Defect Report List #530 Must elements of a string be contiguous?、Cringe not: Vectors are guaranteed to be contiguousコメントも参照のこと。
まとめ:
C++98/03標準ライブラリ仕様上、実現不可能。連続性が保障されている配列型かstd::vector
で代用のこと。- C++11標準ライブラリ仕様ならば安全に実現可能。ただしNUL終端文字の扱いに注意。
- 注意:C++11準拠コンパイラでも、標準ライブラリ内部実装はC++03相当というケースに留意。*2
下記例のようなレガシーAPIに対して、配列の代わりに std::string
オブジェクトを文字列取得バッファとして渡す方法。
// バッファsにNUL終端文字列を取得(size=バッファ長) // 戻り値:バッファに格納された文字数(NUL文字を除く) int legacy_get_string_api(char *s, int size); // plain C #define BUFSIZE /*適当なサイズ*/ char buf[BUFSIZE]; legacy_get_string_api( buf, BUFSIZE ); // 最大(BUFSIZE - 1)文字を取得
C++98/03
C++98/03標準ライブラリでは、std::string の内部実装として「不連続メモリ領域に文字データを保持する(rope)」などの最適化技法が許容されていた。このため string::operator[] 結果のアドレスをとっても、連続した内部バッファ領域を指すという保証がなく、legacy_get_string_api() と組み合わせて利用することはできない。
→ 2013-08-30追記:標準規格による明確な保証がないが、実際の処理系では問題無く動作すると期待できる?NUL終端文字に関する注意点はC++11と同様。
// C++03準拠標準ライブラリ #include <string> std::string buf(BUFSIZE, '\0'); legacy_get_string_api( &buf[0], BUFSIZE ); // NG!! // &buf[0], &buf[1], &buf[2],... は連続配置されている保証がない // 上記例では不定メモリ領域を上書きして壊す危険性がある
std::vector
クラスであれば連続した内部バッファ領域を取得でき、ポインタ値を legacy_get_string_api()
に直接渡せる。ただし別途 std::string
クラスへの変換処理が必要なため、この string
オブジェクトの構築処理と vector
/string
で2倍メモリが必要になるオーバーヘッドは不可避。
#include <vector> #include <string> std::vector<char> buf(BUFSIZE); int len = legacy_get_string_api( &buf[0], BUFSIZE ); // OK std::string str(&buf[0], len); // vectorからstringに変換
C++11
C++11標準ライブラリでは、std::string
内部バッファは連続領域に配置されることが保証される(21.4.5/p2)。また c_str()
および data()
メンバ関数により、いつでもNUL終端された変更不可文字列へのポインタ(const char*
)を取得できる*3。このため、長さ N の文字列を保持する string
内部バッファでは、実際にはNUL終端文字分を含めた (N + 1) バイト確保されていると推測される。(この場合でも、size()
メンバ関数は文字列長 N を返す。)
一方C++11標準規格によれば、文字位置 0≦n<size()
ならば string
オブジェクト外部から書き換え可能だが、NUL終端文字が格納されている n=size()
位置は書き換えが禁止される。例:文字列 "ABC"
を格納した string
オブジェクトに対して、s[0]='a'
や*(s.begin()+2)='c'
などは可能だが、たとえNUL終端文字の上書き(s[3]='\0'
)であってもs[3]
の書き換えは未定義動作(undefined behavior)を引き起こす*4。(21.4.5/p2)
→ 2021-02-03追記:C++17以降はstring
内部バッファのNUL終端文字位置へのNUL終端文字上書きに限って特例許容される。詳細はyumetodoさんによる こちらの記事 を参照のこと。
// C++11準拠標準ライブラリ #include <string> std::string buf(BUFSIZE, '\0'); int len = legacy_get_string_api( &buf[0], BUFSIZE ); // OK buf.resize(len); // BUFSIZE=10 かつ 文字列"123456789"/len=9 が取得できた場合、 // resize前の内部バッファ={"123456789\0",'\0'}と推測される。
N3337 21.4.1/p5, 21.4.5/p1-2より引用(下線部は強調)。
5 The char-like objects in a
basic_string
object shall be stored contiguously. That is, for anybasic_string
objects
, the identity&*(s.begin() + n) == &*s.begin() + n
shall hold for all values ofn
such that0 <= n < s.size()
.
const_reference operator[](size_type pos) const;
reference operator[](size_type pos);
1 Requires:pos <= size()
.
2 Returns:*(begin() + pos)
ifpos < size()
. Otherwise, returns a reference to an object of typecharT
with valuecharT()
, where modifying the object leads to undefined behavior.
おまけ:Microsoft way
// Microsoft MFC/ATL #include <atlstr.h> CString szBuffer; char *p = szBuffer.GetBuffer(BUFSIZE); legacy_get_string_api(p, BUFSIZE); szBuffer.ReleaseBuffer();
関連URL:
*1:正確にはstd::basic_stringクラステンプレート。本記事では簡単のため std::string で説明する。
*2:gcc/libstdc++など。http://gcc.gnu.org/ml/gcc/2011-10/msg00115.htmlも参照のこと。
*3:c_str()およびdata()はconstメンバ関数であり、C++11標準規格ではこれらを呼び出しても “stringオブジェクト内の文字要素を指すポインタ値” が無効化されないことが保証される(N3337 21.4.1/p6)。なおC++03以前では、明示的に「c_str()またはdata()メンバ関数呼び出しでポインタ値が無効されうる」と定義されていた(ISO C++03 21.3/p5)。
*4:NUL終端文字位置はmodify操作のみ禁止される。s[3]=='\0' などのreadアクセスであれば問題ない。