failureおよびninitializedの特別な値を返すことができるようにしたい関数があります(成功するとポインターを返します)。
現在、失敗した場合はNULL
を返し、-1
初期化されていない場合、これは機能しているようです...しかし、システムをだましている可能性があります。 IIRC、アドレスは常にポジティブですよね? (コンパイラーはアドレスを-1に設定することを許可しているので、これは奇妙に思えます)。
私が持っていたもう1つのアイデアは(-1が危険だった場合)、malloc
a char @
グローバルスコープ、およびそのアドレスをセンチネルとして使用します。
いいえ、アドレスは常に正であるとは限りません。x86_64では、ポインターは符号拡張され、アドレス空間は0を中心に対称的にクラスター化されます(ただし、「負の」アドレスはカーネルアドレスであるのが通常です)。
ただし、Cは、同じオブジェクトの一部であるポインター間、または配列の終わりを過ぎたポインター間の_<
_および_>
_ポインター比較の意味のみを定義するため、要点はほとんど意味がありません。完全に異なるオブジェクトへのポインタは、正確に等しい場合を除いて、意味のある比較はできません。少なくとも標準のCでは、if (p < NULL)
には明確に定義されたセマンティクスがありません。
静的ストレージ期間を持つダミーオブジェクトを作成し、そのアドレスをunintialised
値として使用する必要があります。
_extern char uninit_sentinel;
#define UNINITIALISED ((void *)&uninit_sentinel)
_
プログラム全体で単一の一意のアドレスを持つことが保証されています。
ポインタの有効な値は完全に実装に依存するため、はい、ポインタアドレスcouldは負です。
ただし、さらに重要なのは、(可能な実装の選択肢の例として)32ビットのポインターサイズを持つ32ビットプラットフォームを使用している場合を検討することです。その32ビット値で表すことができる値は、有効なポインターである可能性があります。 nullポインター以外のポインター値は、オブジェクトへの有効なポインターである可能性があります。
特定のユースケースでは、ステータスコードを返し、おそらく関数へのパラメーターとしてポインターを使用することを検討する必要があります。
特別な値を戻り値に多重化しようとするのは一般的に悪い設計です...あなたは単一の値でやりすぎを試みています。戻り値ではなく、引数を介して「成功ポインタ」を返す方がクリーンです。これにより、説明するすべての条件の戻り値に、競合しないスペースがたくさん残ります。
int SomeFunction(SomeType **p)
{
*p = NULL;
if (/* check for uninitialized ... */)
return UNINITIALIZED;
if (/* check for failure ... */)
return FAILURE;
*p = yourValue;
return SUCCESS;
}
また、一般的な引数チェックを実行する必要があります(「p」がNULLでないことを確認してください)。
C言語は、ポインターの「否定性」の概念を定義していません。 「負である」という特性は、主に算術的なものであり、ポインタ型の値にはまったく適用できません。
ポインタを返す関数がある場合、その関数から-1
の値を意味のある形で返すことはできません。 C言語では、整数値(ゼロ以外)は暗黙的にポインター型に変換できません。ポインタを返す関数から-1
を返そうとすると、すぐに制約違反になり、診断メッセージが表示されます。要するに、それはエラーです。コンパイラがそれを許可する場合、それは単にその制約をあまり厳密に強制しないことを意味します(ほとんどの場合、以前の標準コードとの互換性のためにそれを行います)。
明示的なキャストによって-1
の値をポインタ型に強制すると、キャストの結果は実装によって定義されます。言語自体はそれについて何の保証もしません。他の有効なポインタ値と同じであることが簡単にわかる場合があります。
予約済みのポインタ値を作成する場合は、malloc
する必要はありません。目的のタイプのグローバル変数を簡単に宣言し、そのアドレスを予約値として使用できます。ユニークであることが保証されています。
符号なし整数が負になる可能性があるように、ポインタは負になる可能性があります。つまり、確かに、2の補数の解釈では、最上位ビットがオンになっているため、数値を負と解釈できます。
失敗と統一の違いは何ですか。ユニタライズが別の種類の障害ではない場合は、インターフェイスを再設計して、これら2つの条件を分離することをお勧めします。
おそらくこれを行う最良の方法は、パラメーターを介して結果を返すことです。したがって、戻り値はエラーを示すだけです。たとえば、次のように記述します。
void* func();
void* result=func();
if (result==0)
/* handle error */
else if (result==-1)
/* unitialized */
else
/* initialized */
これをに変更します
// sets the *a to the returned object
// *a will be null if the object has not been initialized
// returns true on success, false otherwise
int func(void** a);
void* result;
if (func(&result)){
/* handle error */
return;
}
/*do real stuff now*/
if (!result){
/* initialize */
}
/* continue using the result now that it's been initialized */
ポインタは実装で定義されているため、ポインタの符号を気にする必要はありません。ここでの本当の質問は、 "ポインタを返す関数から特別な値を返す方法は?"です。これについては、質問への回答で詳しく説明しました- さまざまなプラットフォームでのポインタアドレススパン
要約すると、オールワンビットパターン(-1)はすでにスペクトルの最後にあり、データを最初のアドレスにラップアラウンドして格納できないため、ほぼ安全です。ポインタの別の状態を示すために、多くのLinuxシステムコールによっても返されます。したがって、failureとuninitializedだけが必要な場合は、それが良い選択です。
ただし、変数を適切に整列させる必要があるという事実を利用することで、はるかに多くのエラー状態を返すことができます(他のオプションを指定した場合を除く)。たとえば、int32_t
へのポインタでは、下位2ビットは常にゼロです。これは、可能な値の1⁄₄のみが有効なアドレスであり、残りのすべてのビットパターンを使用できるようにすることを意味します。したがって、簡単な解決策は、最下位ビットをチェックすることです。
int* result = func();
if ((uintptr_t)result & 1)
uninitialized();
64ビットシステムにデータを格納するために上位ビットを使用することもできます。 ARMでは、アドレスの上位ビットを無視するようにCPUに指示するフラグがあります。x86では、同様のことはありませんが、正規化する限り、これらのビットを使用できます。逆参照する前。 64ビットポインタで余分な16ビットを使用する を参照してください。
も参照してください
@Jamesはもちろん正しいですが、ポインタが常に絶対メモリアドレスを表すとは限らないことを付け加えておきます。これは理論的には常に正です。ポインタは、メモリ内のあるポイント(多くの場合、スタックまたはフレームポインタ)への相対アドレスも表し、正と負の両方になります。
したがって、最善の策は、関数にポインターへのポインターをパラメーターとして受け入れさせ、実際の関数から結果コードを返しながら、成功したときにそのポインターに有効なポインター値を入力することです。
ジェームズの答えはおそらく正しいですが、もちろん実装の選択について説明しています。あなたが選択できる選択ではありません。
個人的には、アドレスは「直感的に」署名されていないと思います。 nullポインタよりも小さいと比較するポインタを見つけるのは間違っているように思われます。ただし、~0
と-1
は、同じ整数型の場合、同じ値を返します。直感的に符号なしの場合、~0
はより直感的な特殊なケースの値を作成する可能性があります。私はこれをエラーケースのunsignedintによく使用します。 本当に違いはありません(デフォルトではゼロはintなので、キャストするまで~0
は-1
です)が、見た目違います。
32ビットシステム上のポインタcan 32ビットすべてを使用しますが、実際には-1
または~0
が実際の割り当てで発生する可能性は非常に低いです。プラットフォーム固有のルールもあります。たとえば、32ビットWindowsでは、プロセスは2GBのアドレス空間しか持てません。また、ある種のフラグをポインターのトップビットにエンコードするコードがたくさんあります(バランスを取るためなど)。バランスの取れたバイナリツリーのフラグ)。
実際には(少なくともx86では)、NULLポインター例外は、NULLポインターを逆参照するだけでなく、より広い範囲のアドレス(たとえば、最初の65kb)によって生成されます。これは、次のようなエラーをキャッチするのに役立ちます
int* x = NULL;
x[10] = 1;
そのため、逆参照時にNULLポインター例外を生成することが保証されているアドレスがさらにあります。ここで、このコード(AndreyT用にコンパイル可能になっています)について考えてみましょう。
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define ERR_NOT_ENOUGH_MEM (int)NULL
#define ERR_NEGATIVE (int)NULL + 1
#define ERR_NOT_DIGIT (int)NULL + 2
char* fn(int i){
if (i < 0)
return (char*)ERR_NEGATIVE;
if (i >= 10)
return (char*)ERR_NOT_DIGIT;
char* rez = (char*)malloc(strlen("Hello World ")+sizeof(char)*2);
if (rez)
sprintf(rez, "Hello World %d", i);
return rez;
};
int main(){
char* rez = fn(3);
switch((int)rez){
case ERR_NOT_ENOUGH_MEM: printf("Not enough memory!\n"); break;
case ERR_NEGATIVE: printf("The parameter was negative\n"); break;
case ERR_NOT_DIGIT: printf("The parameter is not a digit\n"); break;
default: printf("we received %s\n", rez);
};
return 0;
};
これは場合によっては役立つ可能性があります。一部のハーバードアーキテクチャでは機能しませんが、フォンノイマンアーキテクチャでは機能します。
この目的でmalloc
を使用しないでください。不要なメモリが拘束されたままになる可能性があり(たとえば、malloc
が呼び出され、センチネルが上位アドレスに割り当てられたときに大量のメモリがすでに使用されている場合)、メモリデバッガ/リークディテクタを混乱させます。代わりに、ローカルのstatic const char
オブジェクトへのポインタを返すだけです。このポインタは、プログラムが他の方法で取得できるポインタと同じになることはなく、1バイトのbssを浪費するだけです。