web-dev-qa-db-ja.com

memcpy(0,0,0)を実行しても安全であることが保証されていますか?

私はC標準にあまり精通していないので、ご容赦ください。

memcpy(0,0,0)が安全であることが標準で保証されているかどうかを知りたいです。

私が見つけることができる唯一の制限は、メモリ領域が重複している場合、動作は未定義です...

しかし、ここでメモリ領域が重複していると考えることができますか?

70
Matthieu M.

私はC標準(ISO/IEC 9899:1999)のドラフト版を持っていますが、その呼び出しについておもしろいことを言っています。まず第一に、memcpyに関して(§7.21.1/ 2)に言及しています。

size_t nとして宣言された引数が関数の配列の長さを指定する場合、nはその関数の呼び出しで値ゼロを持つことができます。この副次節の特定の関数の説明で特に明記されていない限り、そのような呼び出しのポインター引数は、7.1.4で説明されているように、有効な値を保持しなければなりません。このような呼び出しでは、文字を見つける関数は出現しません。2つの文字シーケンスを比較する関数はゼロを返し、文字をコピーする関数はゼロ文字をコピーします。

ここに示されているリファレンスは、これを指し示しています。

関数への引数に無効な値(関数のドメイン外の値、プログラムのアドレス空間外のポインター、またはヌルポインター、またはnonへのポインターなど)がある場合対応するパラメーターがconst修飾されていない場合は変更可能なストレージ、または可変数の引数を持つ関数では予期されないタイプ(昇格後)動作は未定義

したがって、C仕様によると、

memcpy(0, 0, 0)

nullポインターは「無効な値」と見なされるため、未定義の動作になります。

そうは言っても、memcpyの実際の実装が壊れた場合、私が考えることができる直感的な実装のほとんどは、0バイトをコピーすると言ってもまったく何もしないので、まったく驚きます。

66
templatetypedef

楽しみのために、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)を呼び出すことができると、これらの標準の便利な拡張機能になることがあります。

21
user1998586

Git 2.14.xで見られるmemmoveのこの使用法も検討できます(2017年第3四半期)

commit 168e635 (2017年7月16日)、および commit 1773664commit f331ab9commit 578398 (2017年7月15日)を参照してください) RenéScharfe(rscharfe によって。
ジュニオC浜野-gitster- in commit 32f9025 、2017年8月11日)

ヘルパーマクロ_MOVE_ARRAY_ を使用します。このマクロは、指定された要素数に基づいてサイズを計算し、その数がゼロのときにNULLポインターをサポートします。
Raw 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_はコンパイル時の条件であり、trueでなければなりません)。
条件が真でない場合、またはコンパイラーによって評価できない場合、コンパイルは失敗します。

_#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))
_
0
VonC