Cの任意のブロックの慣用的な使用とは何ですか?


15

ブロックは、実行されるステートメントのリストです。ブロックがCで現れる場所の例は、whileステートメントの後とifステートメントです

while( boolean expression)
    statement OR block

if (boolean expression)
    statement OR block

Cでは、ブロックをブロック内にネストすることもできます。これを使用して変数名を再利用できます。「x」が本当に好きだとします

int x = 0;
while (x < 10)
{
    {
        int x = 5;
        printf("%d",x)
    }
    x = x+1;
}

5を10回印刷します。変数名の数を少なく保つことが望ましい状況を見ることができたと思います。おそらくマクロ展開で。ただし、この機能が必要な理由はわかりません。誰でも私がこの機能の使用法を理解するのを手伝ってくれますか?


1
正直なところ、C構文を理解しようとしているだけで、興味があります。
ジョナサンギャラガー

Cの精神は、プログラマを信頼することです。プログラマーは、何か素晴らしいものを作成したり、ひどいものを作成したりすることができます。個人的には、オーバーロードされた変数名は好きではありませんが、別のプログラマーは好きかもしれません。1日の終わりに、最小限のバグで目的を達成している場合...なぜ議論する必要があるのでしょうか?
いじるビット

1
それは私がCから得た感覚です。私はすべて、さまざまなスタイルのコーディングをサポートする構文を求めています(言語のセマンティクスが大丈夫である限り)。それだけです...私はこれを見て、私の即時の応答は、新しい名前を持つブロック内のすべての変数の名前を変更するソースからソースへの変換を適用し、ブロックを完全にフラット化することができました。何かを取り除くことができると思うときはいつでも、私が見逃した何かがあると思います。
ジョナサンギャラガー

面白いタイミング、非Cプログラマーとして、私は今日Cコードでこの構文に出くわし、それが何のためにあるのか興味がありました。聞いてくれてうれしいです。
ブランドン

回答:


8

アイデアは、変数名の数を少なくしたり、名前の再利用を奨励することではなく、変数の範囲を制限することです。あなたが持っている場合:

int x = 0;
//...
{
    int y = 3;
    //...
}
//...

の範囲はyブロックに限定されます。つまり、ブロックの前でも後でも、そのことを忘れることができます。これは、ループと条件に関連して最も頻繁に使用されます。また、C ++などのCライクな言語でも頻繁に見られます。C++では、変数がスコープ外になると破壊されます。


ブロックは、条件、ループ、および関数本体で自然に発生すると思います。私が興味を持っているのは、Cではどこにでもブロックを配置できるということです。C ++についての2番目のコメントは興味深いものです。スコープを離れると破壊されます。これはガベージコレクションのみを参照していますか、それとも次の別の使用方法です。異なる「セクション」を持つ関数本体を使用し、変数スペースの破壊をトリガーしてメモリフットプリントを制御するブロックを使用できますか?
ジョナサンギャラガー

1
私の知る限り、Cは関数内のすべての変数にすべてのメモリを事前に割り当てます。機能を介してそれらの出口スコープ途中を持つことはC.にはパフォーマンス上の利点は、同様に「のみ時には」関数への呼び出し時にメモリフットプリントを変更しない変数がスコープを入力持たない必要があります
Gankro

@Gankro複数の排他的なネストされたスコープがある場合、影響があります。コンパイラは、これらの各スコープ内の変数に対して、事前に割り当てられた一意のメモリチャンクを再利用できます。もちろん、気付かない理由は、単一の任意のスコープが既に関数に抽出する必要がある兆候である場合、2つ以上の任意のスコープがリファクタリングする必要があることを明確に示すからです。それでも、場合によっては、スイッチケースブロックなどの適切なソリューションとして表示されます。
TNE

16

昔のCでは、新しい変数は新しいブロックでしか宣言できなかったからです。

このようにして、プログラマは関数をリークせずにスタックの使用を最小限に抑えることなく、関数の途中に新しい変数を導入できます。

今日のオプティマイザでは、それは役に立たず、独自の機能でブロックを抽出することを検討する必要がある兆候です。

switchステートメントでは、ケースを独自のブロックで囲み、二重宣言を回避すると便利です。

C ++では、たとえばRAIIロックガードや、実行範囲外になったときにデストラクタがリリースロックを実行し、それでもクリティカルセクションの外部で他の処理を実行するのに役立ちます。


2
RAIIロックガードの場合は+1。そうは言っても、この同じ概念は、ルーチンの一部内の大規模なスタックバッファーの割り当て解除にも役立つことはできませんか?私はそれをやったことがないが、間違いなくいくつかの埋め込みコードで発生する可能性が何かのような音...
J Trana

2

私はそれを「任意の」ブロックとは思わないでしょう。これは開発者が使用するのにそれほど意味のある機能ではありませんが、Cがブロックを使用する方法により、同じブロック構造を同じセマンティクスを持つ多くの場所で使用できます。(Cの)ブロックは新しいスコープであり、それを残す変数は削除されます。これは、ブロックの使用方法に関係なく均一です。

他の言語では、これは当てはまりません。これには、示されているように悪用を減らすことができるという利点がありますが、ブロックの動作はコンテキストに応じて異なる動作をします。

CやC ++でスタンドアロンブロックが使用されることはめったにありません。大規模な構造や、接続や破壊を強制する何かを表すオブジェクトがある場合によく見られます。通常、これは、関数があまりにも多くのことを実行している、および/または長すぎることのヒントです。


1
残念ながら、私は定期的にスタンドアローンのブロックを見ています-ほとんどすべての場合、関数が多くのことをしているため、および/または長すぎます。
マッテンツ

また、ロックやその他の共有リソースのラッパーを強制的に破壊するためだけに、C ++でスタンドアロンブロックを定期的に表示および書き込みます。
Jトラナ

2

自明であると思われるプログラミングの原則は、必ずしもそうではないことを理解する必要があります。Cのベストプラクティスは、サンプルの年齢に大きく依存します。Cが最初に導入されたとき、コードを小さな関数に分割するのは効率が悪いと考えられていました。デニス・リッチーは基本的に嘘をつき、関数呼び出しはCでは本当に効率的だった(当時はそうではなかった)、それが人々がそれらをもっと使い始めた理由だったが、Cプログラマーはどういうわけか時期尚早な最適化の文化を完全に超えたことはなかった。

変数の範囲をできるだけ小さく制限すること、今日でも優れたプログラミング手法です。最近では、通常、新しい関数を作成することでそれを行っていますが、関数が高価であると考えられる場合、新しいブロックを導入することは、関数呼び出しのオーバーヘッドなしにスコープを制限する論理的な方法です。

ただし、スコープの先頭ですべての変数を宣言する必要があった20年以上前に、Cでプログラミングを開始しました。switchステートメントのように2つのブロックで次々に再宣言します。はい、ただしシャドウはしません。 たぶん、変数がすでに使用して、特定の名はあなたのような、呼び出しているAPIのための非常に慣用あった場合destsrcではstrcpy、たとえば、。


1

任意のブロックは、計算の特別な場合にのみ使用される中間変数を導入するのに役立ちます。

これは、科学的な計算の一般的なパターンであり、通常、数値の手順は次のとおりです。

  1. 多くのパラメーターまたは中間の量に依存します。
  2. 多くの特別なケースに対処する必要があります。

2番目のポイントのため、任意のブロックを使用するか、補助関数を導入することで達成される、範囲が制限された一時変数を導入すると便利です。

補助機能を導入することのように見えるかもしれないが非常に簡単無い か、盲目的に従うことをお勧め、そうするため、実際にはほとんどメリットがあり、この特定の状況では。

多くのパラメーターと中間の量があるため、これらを補助関数に渡す構造を導入したいと思います。

しかし、私たちは慣習に従って結果を出したいので、1つの補助機能だけでなく、いくつかの補助機能を導入します。したがって、各関数のパラメーターを伝達するアドホック構造を導入し、パラメーターを前後に移動するための多くのコードオーバーヘッドを導入する、すべての変数を含むが一貫性のないビットのグラブパック。いつでもパラメータの半分だけが興味深い意味を持ちます。

したがって、これらの補助構造は一般に面倒であり、それらを使用することは、コードが膨張するか、スコープが広すぎてプログラムの意味を弱める抽象化を選択することを意味します。

補助機能を導入すると、より細かいテスト粒度を導入することでプログラムの単体テストを容易にすることができますが、低レベルの手順ではない単体テストと、手順の数値トレースの比較(numdiffとの)の形での回帰テストを組み合わせることも同様に良い仕事をします。


0

一般に、この方法で変数名を再利用すると、将来のコードの読者に混乱が生じます。内部変数に別の名前を付ける方が良いでしょう。実際、C#言語では、この方法での変数の使用は特に許可されていません。

マクロの展開でネストされたブロックを使用すると、変数の名前の衝突を防ぐのに役立つことがわかりました。


0

通常、そのレベルではスコープを持たないものをスコープするためのものです。これは非常にまれであり、一般的にリファクタリングがより良い選択ですが、過去にswitchステートメントで1回または2回実行しました。

switch(foo) {
   case 1:
      {
         // bar
      }
   case 2:
   case 3:
      // baz
      break;
   case 4:
   case 5:
      // bang
      break;
}

メンテナンスを検討する場合、これらのリファクタリングは、それぞれが数行の長さである限り、すべての実装を一列に並べることとバランスを取る必要があります。関数名のリストよりも、それらをすべて一緒にする方が簡単な場合があります。

私の場合、正しく思い出せば、ほとんどのケースは同じコードのわずかなバリエーションでした。ただし、1つが別のプリプロセッシングだけで同一であったことを除きます。スイッチは最終的にコードの最も単純な形式であり、追加のスコープにより、それ以降のケースで心配する必要のない追加のローカル変数の使用が許可されました(スイッチの前に定義された変数名と誤って重複しているが後で使用されます)。

switchステートメントは、私が遭遇した単なる使用であることに注意してください。他の場所でも同様に適用できると確信していますが、そのように効果的に使用されたことを覚えていません。

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