web-dev-qa-db-ja.com

C質問:(const void *)vs(void *)

const void *void *の違いは何ですか?どのような状況でvoidポインターをconst voidポインターにキャストできますか?

31
snkherv

_const void *_は、変更すべきではないメモリを指します。

_void *_(非定数)は、変更可能なメモリを指します(ただし、_void *_経由ではなく、最初にキャストする必要があります)。

memmove()を使用すると、ソースアドレスは_const void *_にキャストされます。

_void *memmove(void *dst, const void *src, size_t nbytes);
_

これは、無効なポインターを定数の無効なポインターにキャストできる場合の例です。基本的に、ポインターが指すメモリを変更しないことがわかっている場合はいつでも実行できます(定数に変換)。これは、無効なポインターだけでなく、あらゆるポインターに適用されます。

他の方法(定数ポインターから非定数ポインターへ)の変換は、はるかに危険な演習です。実際に指しているメモリが変更可能であるという保証はありません。たとえば、文字列リテラルは読み取り専用(定数)メモリに格納できます。キャストで定数を失い、文字列を変更しようとすると、セグメンテーションエラーまたはそれに相当するエラーが発生する可能性があります-プログラムが突然停止しますあなたの管理下にありません。これは良いことではありません。そのため、実際にコンパイラーに嘘をついても大丈夫だと確信せずに、ポインターを定数から非定数に変更しないでください。コンパイラーは嘘をつくことを嫌い、通常最も不便な瞬間に自分自身を取り戻すことができることに注意してください(上司、上司、上司の前で重要な見込み客にプログラムをデモンストレーションするときなど) )。

31

void *const void *にキャストすることは完全に合理的であり、コンパイラは暗黙のうちに背後で暗黙のうちに行う必要がありますが、その逆は危険であり、回避する必要があります。

関数がconstポインターを取る場合、constまたは非const値を自由に渡すことができます。 constポインタを取るとは、関数がメモリを変更しないことを宣言することです。

例:([〜#〜] danger [〜#〜]とマークされた行はコンパイラエラーをスローすることに注意してください)

const void *p_const;
void *p_buffer;

// const pointers are allowed to hold static memory
p_const = "Foo"; // allowed
p_buffer = "Foo"; // DANGER!!!

// casting to const is implicit
p_const = malloc(LEN); // allowed - implicit cast
p_buffer = malloc(LEN); // allowed

// casting to const implicit again
write(STDOUT, p_const, LEN); // allowed
write(STDOUT, p_buffer, LEN); // also allowed - implicit cast

// const memory cannot be used where mutable memory is expected
read(0, p_buffer, LEN); // allowed
read(0, p_const, LEN); // DANGER!!

// To make the above more obivous, we'll skip the intermediate variable
// and place instead what it holds
read(0, malloc(LEN), LEN); // allowed - useless but at least no crashes
read(0, "foo", 4); // DANGER!!!

一般的な規則として、作成する関数が変更しない値へのポインターを受け取る場合、関数のシグネチャはconstポインターを使用する必要があります。 constとして宣言されていないポインターを使用することは、ポイントしているメモリーを変更できることを意味します。

もう一つの例:

void do_something(const void* ptr, int length);

// Since the signature is a const pointer, I know I can call it like this:
do_something("foo",4);

逆に、関数が非定数ポインターを呼び出すと、それを許可する必要があります。

void do_something(void* ptr, int length);

// This tells me that the function may overwrite my value.
// The safe solution therefore looks more like this:

char *myptr = char[4];
memcpy(myptr,"foo",4);    
do_something(myptr,4);

同様に、constポインターを非constポインターにキャストする必要がある状況に陥った場合、ポインターの値をメモリの可変部分に複製する必要があります。オリジナルではなく、重複を関数に渡します。それが頭痛の種のように聞こえる場合、それはそうであるからです。そのような状況に陥った場合、おそらく何か間違ったことをしているでしょう。

概念的には、変数が「値」を保持している場合、おそらくconstポインターです。代わりに「バッファ」を保持している場合は、const以外のポインタです。

関数シグネチャのポインタは、そのメモリに書き込むつもりでない限り、alwaysconstと宣言する必要があります。このルールに従うことで、プログラムのロジックに壊滅的な問題を回避できます。

6年間プログラミングをするまで、この単純なルールを理解していませんでした。

7
tylerl