ビットごとのAND演算子を使用した配列宣言へのCポインター


9

次のコードを理解したいと思います。

//...
#define _C 0x20
extern const char *_ctype_;
//...
__only_inline int iscntrl(int _c)
{
    return (_c == -1 ? 0 : ((_ctype_ + 1)[(unsigned char)_c] & _C));
}

これは、obenbsdオペレーティングシステムのソースコードのctype.hファイルに由来します。この関数は、charがASCII文字の範囲内の制御文字または印刷可能な文字であるかどうかをチェックします。これが私の現在の考えの連鎖です:

  1. iscntrl( 'a')が呼び出され、 'a'がその整数値に変換されます
  2. 最初に_cが-1かどうかを確認し、次に0を返します...
  3. 未定義のポインタが指すアドレスを1だけインクリメントします
  4. このアドレスを長さの配列へのポインタとして宣言する(unsigned char)((int) 'a')
  5. ビットごとのand演算子を_C(0x20)と配列(???)に適用します。

どういうわけか、奇妙なことに、それは機能し、0が返されるたびに、指定されたchar _cは印刷可能な文字ではありません。それ以外の場合、それが印刷可能である場合、関数は特別な関心のない整数値を返すだけです。私の理解の問題は、ステップ3、4(少し)および5にあります。

助けてくれてありがとう


1
_ctype_基本的にはビットマスクの配列です。関心のあるキャラクターによって索引付けされています。つまり_ctype_['A']、「alpha」と「uppercase」に_ctype_['a']対応するビットが含まれ、「alpha」と「lowercase」に _ctype_['1']対応するビットが含まれ、「digit」に対応するビットが含まれるなどです。0x20「control」に対応するビットは。しかし、何らかの理由で_ctype_配列が1だけオフセットされているため、のビット'a'は実際ににあり_ctype_['a'+1]ます。(それはおそらくEOF、追加のテストがなくても機能させるためでした。)
Steve Summit

キャスト先(unsigned char)は、文字が署名され、否定的である可能性を処理することです。
Steve Summit

回答:


3

_ctype_はシンボルテーブルの制限された内部バージョンであるように見えますが、これは印刷できないため、+ 1インデックス0の保存を気にしなかったのではないかと思います。または、Cのカスタムのように、0インデックスではなく1インデックステーブルを使用している可能性があります。

C標準では、すべてのctype.h関数に対してこれを指示しています。

すべての場合において、引数はでありint、その値はとして表現可能であるunsigned charか、マクロの値と等しいEOF

コードを段階的に実行します。

  • int iscntrl(int _c)int種類は本当に文字ですが、すべてのctype.h関数は、ハンドルに必要とされているEOF彼らがしなければならないので、int
  • チェックに対しては-1に対してチェックされEOF、それが値を持っているので、-1
  • _ctype+1 配列項目のアドレスを取得するためのポインタ演算です。
  • [(unsigned char)_c]は、単にその配列の配列アクセスであり、キャストは、として表されるパラメーターの標準要件を適用するために存在しますunsigned char。注charこれは防御的プログラミングがあるので、実際には、負の値を保持することができます。[]配列アクセスの結果は、内部シンボルテーブルからの単一の文字です。
  • &マスキングはシンボルテーブルから文字の特定のグループを取得することがあります。明らかに、ビット5が設定されたすべての文字(マスク0x20)は制御文字です。テーブルを見ない限り、これを理解することはできません。
  • ビット5が設定されているものはすべて、0以外の値である0x20でマスクされた値を返します。これは、ブール値がtrueの場合にゼロ以外の値を返す関数の要件を示しています。

キャストが値をとして表すことができるという標準的な要件を述べることは正しくありませんunsigned char。標準では、ルーチンが呼び出されたときに、値がすでに*unsigned charまたはとして表現可能である必要がありますEOF。キャストは「防御的」プログラミングとしてのみ機能します:マクロを使用するときに責任があったときに値を渡すために、署名されたchar(またはsigned char)を渡すプログラマのエラーを修正します。に-1を使用する実装で-1の値が渡された場合、これはエラーを修正できないことに注意してください。unsigned charctype.hcharEOF
Eric Postpischil

これは、の説明にもなります+ 1。マクロが以前にこの防御的調整を含んでいなかった場合、それは単にとして実装され((_ctype_+1)[_c] & _C)、したがって調整前の値-1から255でインデックスが付けられたテーブルを持っている可能性があります。したがって、最初のエントリはスキップされず、目的を果たしました。後で誰かが防御的キャストを追加したときに、EOF値−1はそのキャストでは機能しないため、特別に処理するための条件演算子を追加しました。
Eric Postpischil

3

_ctype_257バイトのグローバル配列へのポインターです。何_ctype_[0]に使うのかわかりません。_ctype_[1]_ctype_[256]_は、それぞれ文字0、…、255 _ctype_[c + 1]の文字カテゴリを表しますc。文字のカテゴリを表します。これは、_ctype_ + 1文字(_ctype_ + 1)[c]のカテゴリを表す256文字の配列を指すと同じことcです。

(_ctype_ + 1)[(unsigned char)_c]は宣言ではありません。これは、配列の添字演算子を使用した式です。(unsigned char)_cで始まる配列の位置にアクセスしてい(_ctype_ + 1)ます。

to _cからキャストさintれるコードunsigned charは厳密には必要ありません。ctype関数はキャストされるchar値を受け取りますunsigned charcharOpenBSDでは署名されています)。正しい呼び出しはchar c; … iscntrl((unsigned char)c)です。これらには、バッファオーバーフローがないことを保証するという利点があります。アプリケーションが-1でないiscntrl範囲外の値で呼び出した場合unsigned char、この関数は意味のない値を返すが、少なくとも原因にはなりません。配列の境界外のアドレスに偶然発生したプライベートデータのクラッシュまたはリーク。-1でないchar c; … iscntrl(c)限り、関数が呼び出されても、値は正しいですc

-1の特殊なケースの理由は、それがであるということEOFです。charたとえばgetchar、を操作する多くの標準C関数intは、文字を値として表し、char値を正の範囲にラップし、特別な値EOF == -1を使用して文字を読み取ることができないことを示します。以下のような機能のためにgetcharEOFその名、ファイルの終わりを示すE ND- O F- fを ILE。Eric Postpischilは、コードは元々は正しかったことを示唆しており、return _ctype_[_c + 1]おそらくそれは正しい:_ctype_[0]EOFの価値でしょう。この単純な実装では、関数が誤用された場合にバッファオーバーフローが発生しますが、現在の実装では、前述のようにこれを回避しています。

もしvアレイに見出される値でv & _Cのビットがあれば試験0x20に設定されていますv。配列の値は、文字が含まれているカテゴリのマスク_Cです。制御文字の場合、_U大文字の場合などが設定されます。


(_ctype_ + 1)[_c] C規格で指定された正しい配列インデックス使用します。これは、EOFまたはunsigned char値を渡すのはユーザーの責任であるためです。他の値の動作は、C標準では定義されていません。キャストは、C標準で要求される動作を実装するためには機能しません。これは、プログラマが誤って負の文字値を渡すことによって引き起こされるバグを防ぐための対策です。ただし、-1の文字値は必ずとして扱われるため、不完全であるか正しくありません(修正できません)EOF
Eric Postpischil

これは、の説明にもなります+ 1。マクロが以前にこの防御的調整を含んでいなかった場合、それは単にとして実装され((_ctype_+1)[_c] & _C)、したがって調整前の値-1から255でインデックスが付けられたテーブルを持っている可能性があります。したがって、最初のエントリはスキップされず、目的を果たしました。後で誰かが防御的キャストを追加したときに、EOF値−1はそのキャストでは機能しないため、特別に処理するための条件演算子を追加しました。
Eric Postpischil

2

ステップ3から始めます。

未定義のポインタが指すアドレスを1 増やします

ポインターは未定義ではありません。他のコンパイル単位で定義されているだけです。そのextern部分がコンパイラに伝えていることです。したがって、すべてのファイルがリンクされると、リンカーはそのファイルへの参照を解決します。

それでそれは何を指していますか?

各文字に関する情報を含む配列を指します。各文字には独自のエントリがあります。エントリは、キャラクターの特性のビットマップ表現です。例:ビット5が設定されている場合、その文字は制御文字であることを意味します。別の例:ビット0が設定されている場合、その文字は大文字です。

のようなもの(_ctype_ + 1)['x']がに適用される特性を取得します'x'。次に、ビットごとに実行され、ビット5が設定されているかどうかを確認します。つまり、ビット5が制御文字かどうかを確認します。

1を追加する理由は、実際のインデックス0が特別な目的のために予約されているためです。


1

ここにあるすべての情報は、ソースコード(およびプログラミング経験)の分析に基づいています。

宣言

extern const char *_ctype_;

const charという名前の場所へのポインタがあることをコンパイラに伝えます_ctype_

(4)このポインタは配列としてアクセスされます。

(_ctype_ + 1)[(unsigned char)_c]

キャスト(unsigned char)_cは、インデックス値がunsigned char(0..255)の範囲にあることを確認します。

ポインタ演算_ctype_ + 1は、配列の位置を1要素だけ効果的にシフトします。彼らがなぜこのように配列を実装したのかはわかりません。範囲を使用して_ctype_[1]... _ctype[256]文字値のために0... 255葉値_ctype_[0]この機能のために使用されていません。(1のオフセットは、いくつかの代替方法で実装できます。)

配列アクセスではchar、文字値を配列インデックスとして使用して(スペースを節約するために)タイプの値を取得します。

(5)ビットごとのAND演算は、値から1ビットを抽出します。

どうやら、配列の値はビットフィールドとして使用され、ビット5(最下位ビット= 0から始まる0から数えます0x20)は「制御文字です」のフラグです。したがって、配列には文字のプロパティを説明するビットフィールド値が含まれています。


彼ら+ 1はにポインタを移動して、1..256ではなく要素にアクセスしていることを明確にしたと思います1..255,0_ctype_[1 + (unsigned char)_c]への暗黙的な変換により、同等intでした。そして_ctype_[(_c & 0xff) + 1]、さらに明確かつ簡潔になっていただろう。
cmaster

0

ここで重要なのは、式(_ctype_ + 1)[(unsigned char)_c]が何をするかを理解することです(それがビット単位のAND演算に送られ& 0x20、結果が得られます!)

短い答え:が_c + 1指す配列の要素を返します_ctype_

どうやって?

まず、あなた_ctype_未定義だと思っているようですが、実際にはそうではありません!ヘッダーはそれを外部変数として宣言しますが、ビルド時にプログラムがリンクされるランタイムライブラリの1つで(ほぼ確実に)定義されます。

構文が配列のインデックス付けにどのように対応するかを示すために、次の短いプログラムを試してみてください(コンパイルさえします)。

#include <stdio.h>
int main() {
    // Code like the following two lines will be defined somewhere in the run-time
    // libraries with which your program is linked, only using _ctype_ in place of _qlist_ ...
    const char list[] = "abcdefghijklmnopqrstuvwxyz";
    const char* _qlist_ = list;
    // These two lines show how expressions like (a)[b] and (a+1)[b] just boil down to
    // a[b] and a[b+1], respectively ...
    char p = (_qlist_)[6];
    char q = (_qlist_ + 1)[6];
    printf("p = %c  q = %c\n", p, q);
    return 0;
}

詳細な説明や説明をお気軽にご依頼ください。


0

で宣言されている関数ctype.hは、タイプのオブジェクトを受け入れますint。引数として使用される文字は、事前に型にキャストされていると見なされますunsigned char。この文字は、文字の特性を決定するテーブルのインデックスとして使用されます。

の値がに含まれている_c == -1場合、チェックが使用されているよう_cですEOF。そうでない場合はEOF、_cがunsigned char型にキャストされます。unsignedcharは、式が指すテーブルでインデックスとして使用されます_ctype_ + 1。マスクで指定されたビット0x20が設定されている場合、その文字は制御記号です。

表現を理解する

(_ctype_ + 1)[(unsigned char)_c]

配列の添え字は次のように定義される後置演算子であることを考慮に入れてください

postfix-expression [ expression ]

次のように書くことはできません

_ctype_ + 1[(unsigned char)_c]

この式は

_ctype_ + ( 1[(unsigned char)_c] )

したがって、式_ctype_ + 1は括弧で囲まれ、1次式を取得します。

だから実際には

pointer[integral_expression]

これは、integral_expressionポインタが(_ctype_ + 1)(gereはポインタarithmetucを使用)でintegral_expressionある式として計算されるインデックスで配列のオブジェクトを生成し、インデックスはexpression (unsigned char)_cです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.