第3章セマンティックトラップ
3.1ポインタと配列
- C言語には1次元配列しかありません。配列内の要素は、任意のタイプのオブジェクトにすることができます。これは、多次元配列を構築するための理論的基礎でもあります。
- 配列の場合、実行できるのは2つだけです。配列のサイズを決定することと、配列のインデックス0にある要素へのポインターを取得することです。配列の添え字操作は、対応するポインター操作と同等です。
- 配列名は最初の要素のアドレスを表し、++または–で操作することはできません。つまり、配列名は定数であり、変更できないため、配列名(表現される値)を変更することはできません。
3.2配列ではないポインタ
エラーを指摘するプログラムは次のとおりです。
char *r;
r = malloc(strlen(s)+strlen(t));
strcpy(r,s);
strcat(r,t);
- Mallocは要求されたメモリを提供できない場合があります。その場合、malloc関数は「メモリ割り当ての失敗」イベントのシグナルとしてnullポインタを返します。
- rに割り当てられたメモリは、使い果たされた後、時間内に解放される必要があります。
- 上記のルーチンは、文字列に終了マーカー'\ 0'も含まれているため、mallocを呼び出すときに十分なメモリを割り当てません。
3.3パラメータとしての配列宣言
1.以下にリストされている2つの式は同等です。
char hello[] = "hello";
printf("%s\n",hello);//写法1
printf("%s\n",&hello);//写法2
理由:配列名helloは、配列helloの最初の要素のアドレスを表します。
2.次の2つの書き方は同等です。
int strlen(char s[])
{
/*具体内容*/
}
int strlen(char *s)
{
/*具体内容*/
}
3.次の2つの書き方に注意してください。
extern char *hello;
extern char hello[];
この2つの書き方は正しいのですが、異なる形で伝えられる意味は完全に矛盾しており、特定の状況に応じて使用する必要があります。
3.4nullポインタは空の文字列ではありません
知らせ:nullポインタはそれを逆参照できません。
同時に、次のように書かないように注意してください。
if(strcmp(p,(char*)0)==0)
···
ライブラリ関数strcmpの実装には、そのポインターパラメーターが何を指しているかを確認する操作、つまりnullポインターを逆参照する操作が含まれるため、この記述方法は違法です。
次のスペルも表示されません。
pがnullポインタであると仮定します
printf(p);
printf("%s",p);
//当然,这两种写法是等价的
この動作は未定義です。
3.5境界計算と非対称境界
-
ループを作成する前に、次のように作成することをお勧めします。
int i = 0; for(i = 0;i < 10; i++) ···
このように書くと、ループの数、つまり10回がよくわかります。
-
配列に10個の要素がある場合、添え字の範囲は0〜9ですが、この要素を参照する必要がないときにこの要素のアドレスのみを参照する必要がある場合は、次のように記述できます。
int arr[10] = {1,2,3,4,5,6,7,8,9,10}; for(int i = 0;&arr[i]<&(arr[10]);i++) ···
このようにして、1から10までの配列要素の数をスムーズに出力できます。
ANSI C規格では、この使用法が明示的に許可されています。実際には存在しないアレイ内の「オーバーラン」要素のアドレスは、アレイ外のアレイが占有するメモリの後に配置され、このアドレスを割り当てと比較に使用できます。もちろん、要素を参照することは違法です。要素の値を実際に読み取った結果は未定義であり、このエラーを検出するコンパイラはほとんどありません。もちろん、この要素を変更しようとすると、必然的にプログラムがクラッシュし、不正なアクセスになります。
3.6評価順序
Cには、所定の評価順序を持つ4つの演算子(&&、||、?:、、)しかありません。==演算子&&および演算子||は、必要な場合にのみ、左側のオペランドを最初に評価し、右側のオペランドを評価します。==演算子?:には3つのオペランドがあります:a?b:c。最初にオペランドaが評価され、次にオペランドbまたはcの値がaの値に従って計算されます(この時点では、前のaの結果に従って、2つの式bまたはcのうち1つだけが実行されます。表現)。コンマ演算子は、最初に左側のオペランドを評価し、次に「値を破棄」してから、右側のオペランドを評価します。
注:split関数の引数はコンマ演算子ではありません。たとえば、関数f(x、y)のxとyの評価順序は定義されていませんが、関数g((x、y))では、最初にx、次にyの明確な順序になっています。後者の例では、関数gにはパラメーターが1つだけあります。このパラメーターの値は、最初にxを評価し、次にxの値を「破棄」し、次にyの値を評価することによって取得されます。
この評価順序が存在することで、一部の「間違った」プログラムが正しくなり、実行後に正しい結果が得られます。
if(count!=0 && sum/count < smallaverage)
···
注:Cの他のすべての演算子がオペランドを評価する順序は、定義されていません。特に、代入演算子は評価の順序を保証するものではありません。
例:次の配列xから配列yへの最初のn個の要素のコピーは、評価の順序について多くの仮定を行うため、正しくありません。
i = 0;
while(i < n)
y[i] = x[i++];
上記のコードは、iがポイントするようにインクリメントされる前にy [i]のアドレスが評価されることを前提としていますが、これは必ずしもそうではなく、コンパイラの特定の実装によって異なります。同様に、次の表記は正しくありません。
i = 0;
while(i<n)
y[i++] = x[i];
次のように変更すると、正常に機能します。
i = 0;
while(i<n)
{
y[i] = x[i];
i++;
}
もちろん、この書き方は次のように省略できます。
for(i = 0;i < n;i++)
y[i] = x[i];
3.9整数のオーバーフロー
C言語で規定されている符号なし整数はオーバーフローしません。結果が表現可能な最大値Mより大きい場合、モジュラス(M + 1)、つまり切り捨てが発生します。
2つの符号付き整数が加算されるとオーバーフローが発生し、オーバーフローの結果は未定義です。
確認する間違った方法は次のとおりです。
if(a + b < 0)
complain();
オーバーフローが発生a+b
すると、結果がどのようになるかについてのすべての仮定が信頼できなくなるためです。
正しい方法は次の2つです。
//方法一:
if((unsigned)a + (unsigned) > INT_MAX)
complain();
//方法二:
if(a > INT_MAX - b)
complain()
3.10関数の戻り値の提供
C言語は、実行が成功したか失敗したかをオペレーティングシステムに通知する値を返すことがよくあります。一般的な解決策はです。戻り値0は、プログラムが正常に実行されたことを示し、ゼロ以外の戻り値は、プログラムが実行に失敗したことを示します。プログラムの最後にreturn0操作を追加することがよくあります。