web-dev-qa-db-ja.com

変数のブロックスコープがCに導入されたのはいつですか?

LibreSSLの最近の OBJ_obj2txt の脆弱性( がOpenSMTPD監査中に発見され、OpenSSLに影響を与えない )、メモリリークの問題は、 以前のコードリファクタリング が原因である可能性が高いことに気付きました(ここでblock scoped変数char *bndec関数スコープに代わりに移動されました。

OpenBSDのような古いプロジェクト内のblock scoped変数に対するこの大きな抵抗があることを私は直接知っていますが、他にどのような正当化が必要ですか? char *bndec宣言?

より広く言えば、Cの変数にブロックスコープが導入されたのはいつですか。私が見つけたすべてはそれ が既にC89 の一部であったことです。それはそれが始まった場所ですか、それとも以前の仕様の一部でしたか?

2
cnst

TL; DR:ブロックスコープの問題ではなく、ポインターの処理方法の誤解です。


あなたが投稿した 脆弱性リスト の厚意により、問題のコードを見てみましょう。

_489 int
490 OBJ_obj2txt(char *buf, int buf_len, const ASN1_OBJECT *a, int no_name)
491 {
...
494         char *bndec = NULL;
...
516         len = a->length;
...
519         while (len > 0) {
...
570                         bndec = BN_bn2dec(bl);
571                         if (!bndec)
572                                 goto err;
573                         i = snprintf(buf, buf_len, ".%s", bndec);
...
598         }
...
601         free(bndec);
...
609 }
_

_BN_bn2dec_ の宣言を詳しく調べると、次のコメントが表示されます。

_/* Must 'free' the returned data */
_

あなたがブロックスコーピングの問題として見ているものは、私は根本的な問題としてポインタ操作の理解が不十分だと思います。

この 差分リビジョン は、_*bndec_がwhileループの内側からwhileループの外側に移動されたときを示しています。このエラーは、free(bndec)ステートメントがループの外に移動されたときにも発生しました。

この関数に対して_*bndec_が宣言されている場所は少し関係ありません。 whileループの外側でも内側でも安全に定義できます。メモリが正しく機能している場合、実際の宣言はとにかくコンパイラによって関数の先頭に移動されます。

ループ内で_*bndec_を宣言しても、free()呼び出しがループ外に残っていると、エラーが発生するだけです。そのエラーが、リファクタリングロジックの背後にある誤りをプログラマーに警告するのに十分であったかどうかはまだ不明です。

ほとんどのプログラマーとポインターに関しては少し悲観論者なので、範囲外の変数エラーにアクセスするfreeは、彼らが彼らの方法のエラーを発見するのに役立たなかったと思います。代わりに、変数宣言をブロックの外に移動するだけでエラーを解決する素朴な(そして正しくない)解決策をとっていました。そして、それが私がこの根本的な原因がブロックスコーピングの問題であるという主張に同意しない理由です。

Cにガベージコレクションがあった場合、この場合はブロックスコープの方がより価値があります。しかし、そうではなく、その考え方は無意味な推測になります。


残念ながら、リファクタリングプログラマがfree(bndec)ステートメントを移動することを選択した理由は、実際にはわかりません。彼らがチェックインコメントを介して残したすべてはこれでした:

この厄介な関数で、文字列とポインタの算術演算の悪夢の一部を解消します。
これにより、一時的な文字列を保持するために使用される厄介なtmp変数とDECIMAL_SIZEハックが取り除かれます。 (元のコードはチェックする前にそれを逆参照するため)bufのかなり無意味なnullチェックを取り除きます。また、長さを計算するために戻り値を使用しているときに-1を返す可能性のある異常な可能性も取り除きます。すべての失敗の場合は、元のコードの最初のエラーの場合のように0と空の文字列を返します。

そして、私はクリーンなコードへの彼らの献身を尊重していますが、彼らがコードからさまざまなチェックを削除することに完全に同意するかどうかはわかりません。かなり大規模な開発チームでかなりの量のCコードを記述した経験から、防御的なコーディングの実践はほとんど常に保証されているという経験があります。そして、開発者はこれらの防御的慣行をaddするための時間費用を正当化できないかもしれませんが、私はこれまでに有効な根拠を見つけたとは言えませんremoveそれらの防御。

リファクタリングプログラマーと同じ量の誇張を掘り下げるために、私は彼らのチェックインコメントを見つけて、あまりにも多くの傲慢さと、十分な謙虚さをぶつけています。その傲慢さは、彼らが彼らが「彼らが何をしていたかを知っている」と仮定するようになった原因である可能性が高いです (TM)free(bndec)ステートメントを移動する際に、彼らはそれがコードに何をするのかを精神的にウォークしませんでした。

このエラーは、ブロックスコーピングのためではなく、ポインターの動作方法を理解できなかったこと、およびポインターとメモリの関係を理解できなかったことが原因で、リファクタリングによって導入されました。


ブロックスコーピングがこの問題に無関係である理由を確立したと思いますが、_"great resistance to block scoped variables within old-school projects"_とみなされるものに対処したいと思います。そして、私の知る限りでは、ANSI Cは常に変数に使用できるブロックスコープを持っています。手足を出して、私はK&R Cにもそれがあったと思います。

Cは初心者や初心者にとって穏やかな言語ではないことに注意してください。 Bおよびアセンブリレベルのプログラミングに対する抽象化の追加レイヤーを提供するために作成されました。 Cで作成するプログラマーは、生成される基になるアセンブラーを理解していることが求められていました。たとえば、変数は自動的に初期化されず、プログラマーの多くはその初期化の欠如によってやられました。 Cは、プログラマがその変数宣言がメモリ内で何にマップするかを理解することを期待しています。 Cはまた、「良い形」と見なされる不文法のルールを公平に共有しています。

すべての変数宣言を移動することは、それらの未記述のスタイルルールの1つであり、実用的な理由は、自動初期化の欠如に戻ります。統制のとれたCプログラマーは、関数の最初にすべての変数を宣言したので、すばやく視覚的にスキャンして、宣言されたすべての変数が初期化されたことを確認できました。これは、一般的な間違い(たとえば、初期化の欠如)が回避されたことを確認する簡単な方法です。

7
user53019

第6版Unix(1975年5月、第2巻「Unixタイムシェアリングシステムで使用するドキュメント」というタイトルの)に付属する Cリファレンスマニュアル では、変数宣言を含めることができる複合ステートメントはfunction-statement

function-bodytype-decl-list関数ステートメント

function-statement:{declaration-listoptstatement-list}

K&Rの最初の版( "Cプログラミング言語"、1978年)では、以下を読むことができます。

変数の宣言(初期化を含む)は、関数を開始するものだけでなく、任意の複合ステートメントを導入する左中括弧の後に続く場合があります

この仕様は、ブロックスコープ変数がおそらく最近のよく知られていない機能(1978)であったというヒントです。

参照 デニス・M・リッチーのページ

5
manlio

もともとそこにあると思います。 {}は、制御構造構文または関数定義の一部であるだけでなく、複合ステートメント(ブロック)も定義します。

これは page で言及されています:「PDP-11 Cの文法が単一のステートメントを必要とするところはどこでもブロックを使用できます。」

したがって、コンパイラーはそれらを処理するときに余分な作業を行いました。ブロックがどこで開始および終了するかを知るためだけに使用されるわけではありません。

編集してコンテキストを明確にします

wikipedia から、「Cの開発は1972年にPDP-11 Unixシステムで始まった」。

1
imel96

これは25年前に遡りますが、一度に画面に20〜22行のコードしか表示できない画面の制限を思い出すことができます(端末の画面サイズがUnixメインフレームに対応しているため)。同じ名前の関数変数とブロック変数があり、それを認識しない。 gccはこれについて警告する--shadowフラグを導入しましたが、すべてのコンパイラに同等のものがあるわけではありませんでした。

別の問題は、コードの読みやすさに関するものでした。

int j;
for(j=1;j<10;j++)
{
    int i=5;
    i++;
    printf("i=%i\n",i); 
}

当時の多くの開発者は、このループを、ループが作成されるときに設定するだけでなく、ループの周囲で毎回i = 5を設定すると読んでいるようでした。持っている

static int i=5;

ループ内で宣言された場合、混乱の終わりはありません。

0
Michael Shaw