私はC標準にあまり精通していないので、ご容赦ください。
規格で保証されているかどうか知りたいのですが memcpy(0,0,0)
安全が。
私が見つけた唯一の制限は、メモリ領域が重複している場合、動作が定義されていないことです...
しかし、ここでメモリ領域が重複していると見なすことができますか?
私はC標準にあまり精通していないので、ご容赦ください。
規格で保証されているかどうか知りたいのですが memcpy(0,0,0)
安全が。
私が見つけた唯一の制限は、メモリ領域が重複している場合、動作が定義されていないことです...
しかし、ここでメモリ領域が重複していると見なすことができますか?
memcpy(0,0,0)
私が見た中で最も奇妙なCコードの1つであると思うので、とても気に入っています。
memcpy(outp, inp, len)
か?そして、これは、outp
とinp
が動的に割り当てられ、最初に割り当てられるコードで発生する可能性があります0
か?これは、たとえば、p = realloc(p, len+n)
whenp
とlen
areで機能し0
ます。私自身、このようなmemcpy
呼び出しを使用しました。技術的にはUBですが、ノーオペレーションではなく、予期しない実装に遭遇したことはありません。
memcpy(0, 0, 0)
は、静的な呼び出しではなく動的な呼び出しを表すことを目的としている可能性があります...つまり、これらのパラメーター値はリテラルである必要はありません。
回答:
私はC標準のドラフトバージョン(ISO / IEC 9899:1999)を持っていますが、その呼び出しについていくつか楽しいことがあります。まず第一に、それは言及に関しては(§7.21.1/ 2)memcpy
その
size_t
nとして宣言された引数が関数の配列の長さを指定する場合、nはその関数の呼び出しで値ゼロを持つことができます。この節の特定の関数の説明で特に明記されていない限り、そのような呼び出しのポインタ引数は、7.1.4で説明されているように、引き続き有効な値を持っている必要があります。このような呼び出しでは、文字を検索する関数は出現を検出せず、2つの文字シーケンスを比較する関数はゼロを返し、文字をコピーする関数はゼロ文字をコピーします。
ここに示されている参照は、これを示しています。
関数の引数に無効な値(関数のドメイン外の値、プログラムのアドレス空間外の ポインター、nullポインター、または対応するパラメーターの場合の変更不可能なストレージへのポインターなど)がある場合const-qualifiedではない)または可変数の引数を持つ関数で予期されない型(昇格後)の場合、動作は未定義です。
つまり、C仕様によると、
memcpy(0, 0, 0)
nullポインタは「無効な値」と見なされるため、未定義の動作になります。
とは言うmemcpy
ものの、これを行った場合、実際の実装が壊れた場合、私は完全に驚きます。なぜなら、ゼロバイトをコピーすると言った場合、私が考えることができる直感的な実装のほとんどはまったく何もしないからです。
realloc(0, 0)
。ユースケースは似ており、私は両方を使用しました(質問の下の私のコメントを参照してください)。標準がこのUBを作成することは無意味で不幸です。
楽しみのために、gcc-4.9のリリースノートは、オプティマイザがこれらのルールを利用していることを示しており、たとえば、
int copy (int* dest, int* src, size_t nbytes) {
memmove (dest, src, nbytes);
if (src != NULL)
return *src;
return 0;
}
copy(0,0,0)
が呼び出されると、予期しない結果が発生します(https://gcc.gnu.org/gcc-4.9/porting_to.htmlを参照)。
私はgcc-4.9の振る舞いについていくぶんあいまいです。動作は標準に準拠している可能性がありますが、memmove(0,0,0)を呼び出すことができると、これらの標準の拡張として役立つ場合があります。
char *p = 0; int i=something;
、式(p+i)
を評価すると、i
がゼロの場合でも未定義の振る舞いが発生します。
memcpy()
ゼロ以外のカウントを保証する前に、引数に対してポインタ演算を実行できるかどうかは別の質問です[標準を設計している場合p
、nullの場合はp+0
トラップできるが、memcpy(p,p,0)
何もしないことを指定します]。はるかに大きな問題であるIMHOは、ほとんどの未定義の振る舞いの制限がないことです。未定義の振る舞いを実際に表す必要があるものがいくつかありますが(たとえば、呼び出しfree(p)
...
p[0]=1;
)不確定な結果をもたらすものとして指定する必要があるものがたくさんあります(たとえば、無関係なポインター間の関係比較は、他の比較と一致するものとして指定するべきではありませんが、0をもたらすものとして指定する必要がありますまたは1)、または実装定義よりもわずかに緩い動作をもたらすものとして指定する必要があります(コンパイラは、整数オーバーフローなどの考えられるすべての結果を文書化する必要がありますが、特定の場合にどの結果が発生するかは指定しないでください)。
この使用法を検討することもできます memmove
Git 2.14.x(2017年第3四半期)見られる
参照してください168e635コミット(2017年7月16日)を、および1773664をコミットし、f331ab9をコミットし、5783980コミットにより(2017年7月15日)を(ルネScharfe rscharfe
)。
(合併によりJunio C浜野- gitster
-で32f9025コミットし、2017年8月11日)を
指定された要素数に基づいてサイズを計算し、
その数がゼロの場合にポインターをサポートするヘルパーマクロMOVE_ARRAY
を使用しNULL
ます。
を使用した生のmemmove(3)
呼び出しNULL
により、コンパイラーは後のNULL
チェックを(過度に)最適化する可能性があります。
MOVE_ARRAY
重複する可能性のある配列エントリの範囲を移動するための安全で便利なヘルパーを追加します。
要素のサイズを推測し、自動的かつ安全に乗算してバイト単位のサイズを取得し、要素のサイズを比較して基本的な型安全性チェックを行います。memmove(3)
これとは異なり、NULL
0個の要素を移動する場合はポインタをサポートします。
#define MOVE_ARRAY(dst, src, n) move_array((dst), (src), (n), sizeof(*(dst)) + \
BUILD_ASSERT_OR_ZERO(sizeof(*(dst)) == sizeof(*(src))))
static inline void move_array(void *dst, const void *src, size_t n, size_t size)
{
if (n)
memmove(dst, src, st_mult(size, n));
}
例:
- memmove(dst, src, (n) * sizeof(*dst));
+ MOVE_ARRAY(dst, src, n);
これは、使用するマクロBUILD_ASSERT_OR_ZERO
(と表現として、ビルド時の依存性を主張する@cond
真でなければならないコンパイル時の条件です)。
条件が真でない場合、またはコンパイラーが評価できない場合、コンパイルは失敗します。
#define BUILD_ASSERT_OR_ZERO(cond) \
(sizeof(char [1 - 2*!(cond)]) - 1)
例:
#define foo_to_char(foo) \
((char *)(foo) \
+ BUILD_ASSERT_OR_ZERO(offsetof(struct foo, string) == 0))
いいえ、memcpy(0,0,0)
安全ではありません。標準ライブラリは、その呼び出しで失敗しない可能性があります。ただし、テスト環境では、バッファオーバーランやその他の問題を検出するために、いくつかの追加コードがmemcpy()に存在する場合があります。そして、その特別なバージョンのmemcpy()がNULLポインターにどのように反応するかは、まあ、未定義です。