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


80

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

規格で保証されているかどうか知りたいのですが memcpy(0,0,0)安全が。

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

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


7
数学的には、2つの空のセットの共通部分は空です。
ブノワ

(x)libCがあなたに代わってくれるかどうかを確認したかったのですが、asm(ここではelibc / glibc)なので、早朝には少し複雑すぎます:)
Kevin

16
+1この質問は、非常に奇妙なエッジケースであり、memcpy(0,0,0)私が見た中で最も奇妙なCコードの1つであると思うので、とても気に入っています。
templatetypedef

2
@eq本当に知りたいですか、それともあなたが知りたい状況がないことを暗示していますか?実際の呼び出しは、たとえば、である可能性があると考えましたmemcpy(outp, inp, len)か?そして、これは、outpinpが動的に割り当てられ、最初に割り当てられるコードで発生する可能性があります0か?これは、たとえば、p = realloc(p, len+n)whenplenareで機能し0ます。私自身、このようなmemcpy呼び出しを使用しました。技術的にはUBですが、ノーオペレーションではなく、予期しない実装に遭遇したことはありません。
ジムバルター2011年

7
@templatetypedefmemcpy(0, 0, 0)は、静的な呼び出しではなく動的な呼び出しを表すことを目的としている可能性があります...つまり、これらのパラメーター値はリテラルである必要はありません。
ジムバルター2011年

回答:


71

私はC標準のドラフトバージョン(ISO / IEC 9899:1999)を持っていますが、その呼び出しについていくつか楽しいことがあります。まず第一に、それは言及に関しては(§7.21.1/ 2)memcpyその

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

ここに示されている参照は、これを示しています。

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

つまり、C仕様によると、

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

とは言うmemcpyものの、これを行った場合、実際の実装が壊れた場合、私は完全に驚きます。なぜなら、ゼロバイトをコピーすると言った場合、私が考えることができる直感的な実装のほとんどはまったく何もしないからです。


3
ドラフト標準から引用された部分は、最終文書で同一であると断言できます。このような呼び出しで問題が発生することはないはずですが、それでも、信頼している未定義の動作になります。したがって、「保証されているか」に対する答えは「いいえ」です。
DevSolar 2011年

8
本番環境で使用する実装は、そのような呼び出しに対してno-op以外のものを生成しませんが、それ以外の実装は許可され、合理的です...たとえば、Cインタープリターまたはエラーチェック付きの拡張コンパイラーは、不適合なのでお電話ください。もちろん、の場合のように、標準が呼び出しを許可した場合、それは合理的ではありませんrealloc(0, 0)。ユースケースは似ており、私は両方を使用しました(質問の下の私のコメントを参照してください)。標準がこのUBを作成することは無意味で不幸です。
ジムバルター2011年

5
「これを行った場合、memcpyの実際の実装が壊れた場合、私は完全に驚きます」-私はそうするものを使用しました。実際、有効なポインタを使用して長さ0を渡した場合、実際には65536バイトがコピーされます。(そのループは長さをデクリメントしてからテストしました)。
MM

14
@MattMcNabbその実装は壊れています。
ジムバルター2014

3
@MattMcNabb:多分「actual」に「correct」を追加してください。私たちは皆、古いゲットーCライブラリのあまり好きではない思い出を持っていると思います。そして、私たちの多くがそれらの思い出が思い出されたことに感謝しているのかわかりません。:)
tmyklebu 2014

24

楽しみのために、gcc-4.9のリリースノートは、オプティマイザがこれらのルールを利用していることを示しており、たとえば、

copy(0,0,0)が呼び出されると、予期しない結果が発生しますhttps://gcc.gnu.org/gcc-4.9/porting_to.htmlを参照)。

私はgcc-4.9の振る舞いについていくぶんあいまいです。動作は標準に準拠している可能性がありますが、memmove(0,0,0)を呼び出すことができると、これらの標準の拡張として役立つ場合があります。


2
面白い。私はあなたの曖昧さを理解していますが、これがCの最適化の核心です。コンパイラ開発者が特定のルールに従うと想定し、したがっていくつかの最適化が有効であると推測します(ルールに従っている場合)。
Matthieu M.

2
@tmyklebu:与えられた場合char *p = 0; int i=something;、式(p+i)を評価すると、iがゼロの場合でも未定義の振る舞いが発生します。
スーパーキャット2014

1
@tmyklebu:nullポインタトラップで(比較以外の)すべてのポインタ演算を行うのは良いことです。memcpy()ゼロ以外のカウントを保証する前に、引数に対してポインタ演算を実行できるかどうかは別の質問です[標準を設計している場合p、nullの場合はp+0トラップできるが、memcpy(p,p,0)何もしないことを指定します]。はるかに大きな問題であるIMHOは、ほとんどの未定義の振る舞いの制限がないことです。未定義の振る舞いを実際に表す必要があるものがいくつかありますが(たとえば、呼び出しfree(p)...
supercat 2014

1
...そしてその後の実行p[0]=1;)不確定な結果をもたらすものとして指定する必要があるものがたくさんあります(たとえば、無関係なポインター間の関係比較は、他の比較と一致するものとして指定するべきではありませんが、0をもたらすものとして指定する必要がありますまたは1)、または実装定義よりもわずかに緩い動作をもたらすものとして指定する必要があります(コンパイラは、整数オーバーフローなどの考えられるすべての結果を文書化する必要がありますが、特定の場合にどの結果が発生するかは指定しないでください)。
スーパーキャット2014

8
誰か教えてください、「炎上戦争を始めた」
スタックオーバーフロー

0

この使用法を検討することもできます memmoveGit 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)これとは異なり、NULL0個の要素を移動する場合はポインタをサポートします。

これは、使用するマクロBUILD_ASSERT_OR_ZERO(と表現として、ビルド時の依存性を主張する@cond真でなければならないコンパイル時の条件です)。
条件が真でない場合、またはコンパイラーが評価できない場合、コンパイルは失敗します。

例:


2
「賢い」と「ダム」を反意語と考えるオプティマイザーの存在により、nのテストが必要になりますが、memmove(any、any、0)がノーオペレーションになることが保証された実装では、より効率的なコードが一般的に可能です。 。コンパイラーがmemmove()の呼び出しをmemmoveAtLeastOneByte()の呼び出しに置き換えることができない限り、賢い/愚かなコンパイラーの「最適化」を防ぐための回避策は、通常、コンパイラーが排除できない余分な比較をもたらします。
スーパーキャット2017

-3

いいえ、memcpy(0,0,0)安全ではありません。標準ライブラリは、その呼び出しで失敗しない可能性があります。ただし、テスト環境では、バッファオーバーランやその他の問題を検出するために、いくつかの追加コードがmemcpy()に存在する場合があります。そして、その特別なバージョンのmemcpy()がNULLポインターにどのように反応するかは、まあ、未定義です。


標準からの抜粋で、これが安全ではないとすでに指摘している既存の回答にあなたの回答が追加するものは何もわかりません。
MatthieuM.19年

memcpyの特定の実装(「テスト環境」)がmemcpy(0、0、0)で問題を引き起こす可能性がある理由を書きました。私は、それは新しいことだと思います。
KaiPetzke19年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.