次の行(純粋なc)はwindows(win7 64ビット+コードブロック13 + mingw32)およびdebianできれいにコンパイルします(wheezy 32ビット+コードブロック10 + gcc)しかし、kali(64ビット+コードブロック+ gcc)で警告を発生させます。コメントはありますか?同じ行がWindowsとDebianで警告なしでコンパイルされるのに、なぜこの警告が表示されるのですか?
void* foo(void *dst, ...) {
// some code
unsigned int blkLen = sizeof(int); // this line ok.
unsigned int offset = (unsigned int) dst % blkLen; // warning here!
// some code cont...
}
コードブロック内のメッセージは次のとおりです。「error:ポインターから異なるサイズの整数へのキャスト[-Werror = pointer-to-int-cast]」
注:私のコンパイラオプションは-std=c99 -Werror -save-temps
(3つすべてのシステムで同じ)。
編集2:下のプリプロセッサー行を使用して警告なしでコンパイルすることができましたが、@ Keith Thompson(下記参照)がこの問題に関する重要なポイントを持っています。したがって、私の最後の決定はuintptr_t
の方が良い選択です。
編集1:回答してくれたすべての人に感謝します。すべての返信にあるように、問題は32ビット対64ビットの問題です。次のプリプロセッサ行を挿入しました:
#if __linux__ // or #if __GNUC__
#if __x86_64__ || __ppc64__
#define ENVIRONMENT64
#else
#define ENVIRONMENT32
#endif
#else
#if _WIN32
#define ENVIRONMENT32
#else
#define ENVIRONMENT64
#endif
#endif // __linux__
#ifdef ENVIRONMENT64
#define MAX_BLOCK_SIZE unsigned long long int
#else
#define MAX_BLOCK_SIZE unsigned long int
#endif // ENVIRONMENT64
そして、問題の行を次のように置き換えました:
unsigned int offset = (MAX_BLOCK_SIZE) dst % blkLen;
今、すべてがOKのようです。
警告の理由は、int
を介してポインターを往復させようとしている可能性があるとコンパイラーが疑うためです。これは、64ビットマシンが登場する前の一般的な方法であり、安全でも合理的でもありません。もちろん、ここでコンパイラは、あなたがこれをしていないことをはっきりと見ることができ、このような場合に警告を回避するのに十分賢いならいいでしょうが、そうではありません。
警告を回避するクリーンな代替手段、および変換された値が負の場合の誤った結果の別の非常に厄介な問題は、次のとおりです。
unsigned int offset = (uintptr_t) dst % blkLen;
stdint.h
を使用可能にするには、inttypes.h
またはuintptr_t
を含める必要があります。
問題は、void*
ポインターをunsigned int
に変換することは本質的に移植性がないことです。
サイズの可能な違いは問題の一部にすぎません。問題のその部分は、uintptr_t
および<stdint.h>
で定義されているタイプである<inttypes.h>
を使用することで解決できます。 uintptr_t
は、void*
をuintptr_t
に変換して再び元のポインター値(または少なくとも元のポインター値と等しいポインター値)を生成するのに十分な幅であることが保証されます。タイプintptr_t
もあり、これは署名されています。通常、この種の場合は、符号なしの型の方が理にかなっています。 uintptr_t
およびintptr_t
の存在は保証されていませんが、適切な整数型を持つ(C99以降の)実装に存在する必要があります。
しかし、変換されたポインターを保持するのに十分な大きさの整数型を持っている場合でも、結果は必ずしもポインターに戻す以外の意味を持ちません。
C標準では、非規範的な脚注で次のように述べています。
ポインターを整数に、または整数をポインターに変換するためのマッピング関数は、実行環境のアドレス指定構造と一致するように意図されています。
そのアドレス構造が何であるかを知らない限り、これは役に立ちません。
void*
引数のオフセットがblkLen
の次に低い倍数に対して相対的であるかどうかを判断しようとしているようです。つまり、blkLen
-サイズのメモリブロックに関して、ポインタ値がalignedであるかどうかを判断しようとしています。
もしそれが賢明なことだと知っていたら使用しているシステムで、それは問題ありません。ただし、ポインター変換から生じる整数の算術演算は、本質的に移植性がないことに注意する必要があります。
具体例:void*
ポインターが64ビットマシンアドレス(64ビットWordを指す)であり、3ビットバイトオフセットが挿入されたシステム(Crayベクトルマシン)で作業しましたそれ以外の場合は未使用のソフトウェア高次 3ビット。ポインターを整数に変換すると、単に表現がコピーされます。そのような整数の整数演算は、この(明らかにエキゾチックな)表現を考慮しない限り、意味のない結果をもたらす可能性があります。
結論:
使用できる整数型を決定するには、プリプロセッサトリックを再生するのではなく、必ずuintptr_t
を使用する必要があります。コンパイラの実装者は、変換されたポインタ値を安全に保持できる整数型を決定する作業をすでに完了しています。その特定の車輪を再発明する必要はありません。 (警告:<stdint.h>
は1999 ISO標準によってCに追加されました。それを実装していない古代のコンパイラを使用している場合、何らかの#ifdef
ハックを使用する必要があります。ただし、uintptr_t
が使用可能な場合は使用することをお勧めします。C99準拠をテストするために__STDC_VERSION__ >= 199901L
をテストできますが、一部のコンパイラはC99を完全にサポートせずに<stdint.h>
をサポートします。
ポインタを整数に変換し、その値で遊ぶことは移植性がないことに注意する必要があります。それはあなたがそれをするべきではないということではありません。 Cの最大の強みの1つは、必要なときにポータブルでないコードをサポートできることです。
_void *
_を_unsigned int
_にキャストするのは、安全ではないため、その警告がキャッチすることを意図しているからです。ポインターは64ビットにすることができ、int
は32ビットにすることができます。どのプラットフォームでも、sizeof(unsigned int)
はsizeof(void *)
であるとは限りません。代わりに_uintptr_t
_を使用する必要があります。
64ビットアーキテクチャでは、ポインターが64ビット長で、intが32ビット長であるためでしょうか。
試してみてください
void* foo(void *dst, ...) {
// some code
unsigned int blkLen = sizeof(int); // this line ok.
uintptr_t offset = (uintptr_t) dst % blkLen; // warning here!
// some code cont...
}
Intのサイズは実装に依存するため、警告が表示されると思います。たとえば、intは2バイト長または4バイト長です。これが警告の理由かもしれません(間違っている場合は修正してください)。しかし、とにかくポインターでモジュロをしようとしているのはなぜですか。
マクロを作成しましたが、まだ間違っているとは思わないでください。ポインターはunsigned long long intまたは86xおよび64x OSでは32ビットと64ビットになるunsigned long intに変換されますが、変数オフセットは64xおよび86x OSでは32ビットであるunsigned intであるためです。したがって、オフセットをそれぞれのマクロにも変換する必要があると思います。
または、ポインターをlongに変換(つまり、unsigned intからlong)し、オフセットをlongに変換(つまり、unsigned intからlong)することもできます。