Cのvoidポインターのポインター演算


176

特定の型へのポインタを(言うときintcharfloat、...)がインクリメントされ、その値はそのデータ型のサイズだけ増加されます。voidサイズのデータ​​を指すポインターxがインクリメントされた場合、xバイト先をどのように指すのですか?コンパイラーxは、ポインターの値に追加することをどのようにして知っていますか?



3
この質問は、コンパイラー(/ランタイム)がポインターが設定されたオブジェクトのタイプを知っていると想定し、そのサイズをポインターに追加するかのように聞こえます。それは完全な誤解です。アドレスしか知りません。
PJTraill、2015

2
voidサイズのデータ​​を指すポインターxがインクリメントされる場合、どのようにしてxバイト先を指すようになりますか?」そうではありません。なぜそのような質問をする人は尋ねる前にそれらをテストできないのですか-少なくとも、少なくともコンパイルが実際にコンパイルされるかどうかを確認する最低限のことではありますが、そうではありません。-1は、これが+100と-0になったとは信じられません。
underscore_d

回答:


287

最終的な結論:CとC ++の両方でaの算術void*違法です。

GCCは、拡張機能として、それを可能にする、参照に算術演算をvoid-と機能-ポインタ(このセクションでは、マニュアルの「C拡張機能」の章の一部であることに注意してください)。ClangとICC void*は、GCCとの互換性を目的とした算術演算を可能にします。他のコンパイラ(MSVCなど)は、での演算をvoid*許可しません。-pedantic-errorsフラグが指定されている場合、またはフラグが指定されている場合、GCCはそれを許可しません-Werror-pointer-arith(このフラグは、コードベースもMSVCでコンパイルする必要がある場合に役立ちます)。

Cスタンダードが語る

引用はn1256ドラフトから取得されます。

加算操作の状態に関する規格の説明:

6.5.6-2:さらに、両方のオペランドが算術型であるか、一方のオペランドがオブジェクト型へのポインタであり、もう一方が整数型である必要があります。

したがって、ここでの問題はvoid*、「オブジェクト型」へのポインタであるかどうか、または同等にvoid「オブジェクト型」であるかどうかです。「オブジェクトタイプ」の定義は次のとおりです。

6.2.5.1:型は、オブジェクト型オブジェクトを完全に記述する型)、関数型(関数を記述する型)、および不完全型(オブジェクトを記述するが、サイズを決定するために必要な情報がない)に分割されます

そして標準は次のvoidように定義しています:

6.2.5-19:voidタイプは空の値のセットで構成されます。完成できない不完全なタイプです。

voidは不完全なタイプなので、オブジェクトタイプではありません。したがって、これは加算演算の有効なオペランドではありません。

したがって、voidポインターに対してポインター演算を実行することはできません。

ノート

もともと、void*C標準の以下のセクションのために、算術演算が許可されていると考えられていました。

6.2.5-27:void へのポインターは、文字型へのポインターと同じ表現および配置要件を持たなければならない。

しかしながら、

同じ表現と配置の 要件は、関数への引数、関数からの戻り値、および共用体のメンバーとしての互換性を意味します。

この手段だから、printf("%s", x)かと同じ意味を持っているxタイプを持っているchar*void*、それはあなたが上の算術演算を行うことができることを意味するものではありませんvoid*

編集者注:この回答は、最終的な結論を反映するように編集されています。


7
C99標準から:(6.5.6.2)さらに、両方のオペランドが算術型であるか、一方のオペランドがオブジェクト型へのポインターであり、もう一方が整数型である必要があります。(6.2.5.19)void型は空の値のセットで構成されます。完成できない不完全なタイプです。これにより、void*ポインター演算が許可されないことが明らかになったと思います。GCCには、これを可能にする拡張機能があります。
ジョブ

1
回答が役に立たなくなった場合は、削除してください。
カフェ

1
この回答は、無効なポインターが算術用ではないという決定的な証拠を含んでいるため、間違っていたとしても役に立ちました。
ベン・フリン

1
これは良い答えです。正しい結論と必要な引用がありますが、この質問に来た人々は、答えの最後まで読んでいないため、間違った結論に至りました。これをわかりやすくするために編集しました。
ディートリッヒエップ2013年

1
ClangとICCはvoid*算術を許可しません(少なくともデフォルトでは)。
Sergey Podobry、2016年

61

ポインター演算はポインターでは許可されていませんvoid*


16
+1ポインター演算は、(完全な)オブジェクト型へのポインターに対してのみ定義されます。voidある不完全な型定義によって完成することはできません。
2010

1
@schot:その通りです。さらに、ポインタ演算は、配列オブジェクトの要素へのポインタでのみ定義され、演算の結果が同じ配列内の要素またはその配列の最後の要素の1つ後の要素へのポインタである場合にのみ定義されます。これらの条件が満たされない場合、動作は未定義です。(C99標準6.5.6.8より)
ジョブ

1
どうやらgcc 7.3.0ではそうではありません。コンパイラーはp + 1024を受け入れます。ここで、pはvoid *です。結果は((char *)p)+ 1024
zzz777と同じ

@ zzz777これはGCC拡張機能です。投票した回答のトップにあるリンクを参照してください。
ルスラン

19

それをcharポインタにキャストし、ポインタをxバイト先に進めます。


4
なぜわざわざ?それを正しい型にキャストするだけです-これは常に認識されているはずです-それを1ずつインクリメントします。にキャストしchar、をインクリメントしてxから、新しい値を他の型が無意味で未定義の動作であると解釈し直します。
underscore_d

9
に従ってをman 3 qsort持っているはずのソート関数を記述している場合void qsort(void *base, size_t nmemb, size_t size, [snip])、「正しいタイプ」を知る方法はありません
。– alisianoi

15

C標準では許可されていません、ボイドポインタ演算を。しかし、GNU Cはの大きさを考慮することによって許可されている無効です1

C11標準§6.2.5

パラグラフ-19

voidタイプは、値の空のセットを含みます。それは不完全なオブジェクト型に完了することができません。

次のプログラムはGCCコンパイラで正常に動作しています。

#include<stdio.h>

int main()
{
    int arr[2] = {1, 2};
    void *ptr = &arr;
    ptr = ptr + sizeof(int);
    printf("%d\n", *(int *)ptr);
    return 0;
}

他のコンパイラがエラーを生成する可能性があります。



8

ポインタ演算を行う前に、別のタイプのポインタにキャストする必要があります。


7

Voidポインターは、任意のメモリチャンクを指すことができます。したがって、コンパイラは、ボイドポインターでポインター演算を試みたときに、インクリメント/デクリメントするバイト数を認識しません。したがって、voidポインターは、ポインター演算に関与する前に、既知の型に最初に型キャストする必要があります。

void *p = malloc(sizeof(char)*10);
p++; //compiler does how many where to pint the pointer after this increment operation

char * c = (char *)p;
c++;  // compiler will increment the c by 1, since size of char is 1 byte.

-1

コンパイラーは型キャストによって認識します。与えられたvoid *x

  • x+1 に1バイトを追加します x、ポインタは移動しますx+1
  • (int*)x+1 追加する sizeof(int)バイトを、ポインタバイトに移動しますx + sizeof(int)
  • (float*)x+1 アドレス sizeof(float)バイトなど

最初の項目は移植可能ではなく、C / C ++のGalateoに違反していますが、それでもC言語で正しく、ほとんどのコンパイラでコンパイルされ、適切なフラグ(-Wpointer-arithなど)が必要になる可能性があります。


2
Althought the first item is not portable and is against the Galateo of C/C++そうだね。it is nevertheless C-language-correctいいえ。これは二重の考えです!ポインター演算on void *は構文的には不正であり、コンパイルすべきではなく、コンパイルすると未定義の動作を引き起こします。不注意なプログラマーがいくつかの警告を無効にすることでコンパイルすることができる場合、それは言い訳にはなりません。
underscore_d

@underscore_d:ポインターに値をunsigned char*追加するためにキャストする必要があるよりもはるかに便利であるため、一部のコンパイラーは拡張機能としてそれを許可していたと思いsizeofます。
スーパーキャット2016
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.