プログラミングスタイルの質問に

「精神的価値のスケールを降順で見ると、

物事の順序で、存在

不快なものだが、原則として許容され、存在する

耐えられないもの。」 -ミード



悪名高い古典の言葉で「私は沈黙することはできません。」

最近、かなり有名なメーカーのサイトのソースコードを見て、次のコードを見ました
*(unsigned int *)0xf80ff000 &= 0xffffefff;
      
      



これらのメモが遠く離れた「インド」で読まれることを望んでいません(下記のメモを参照)(読み方すら知らないようです)、それでも若いエンジニアに警告したいと思います-これは不可能です。



今日のメモ-1か月以上前にこの投稿をスケッチしました。すべての手がそれを受け入れられる形にするために手を伸ばすことができなかったので、ソースを示すことはできませんが、インターネットはあなたを助けます-そのようなコードはたくさんあります。



あなたがそれをする方法とすべきことを言う文学の海があります(それを読む人は誰でも、たくさんの手紙があります)、私はMISRA標準を強くお勧めしますが、同様のプログラム構成は国境の反対側にあるのでそこにも考慮されていません悪から善。 しかし、そのような構造はまだ見つかっており、子供たちはそれらを見ることができるので(そして、インターネットの正しい検閲はまだ確立されていません)、おそらくこれができない理由をもう一度説明する必要があります。



そもそも、遠いインドの同僚がこのコードフラグメントを作成したかったことがわかります(著者はまったくの悪徳主義者ではなく、コードが中国、アメリカ、またはロシアのプログラマーによって書かれたことを認める準備ができています。ヒンドゥー教のコードは普通の名詞になりました)。 明らかに、16進アドレスf80ff000のワードのビット番号12(0からカウント)をリセットしたかったのです。 ほとんどの場合、外部デバイスのレジスタはそこにあり、ほとんどの場合、レディビットをリセットしますが、これは完全に重要ではありません。 したがって、操作自体は著者を長い投稿に追い込むほどひどいものではありませんが、それでもデブリーフィングを開始します。



まず、1行で、2つのマジック定数が一度に使用されました-アドレス用とデータ用です。 プログラム内のどこにもこのレジスタへの単一のアクセスが存在することは信じられないため(ソースコードで同様の行をさらに見ることができます)、したがって、マジック定数の使用には正当な理由はありません。 ご存知のように、#defineはこのために発明されたもので、最近列挙型リストの定数を使用する必要性について多くの説得力のある資料を見てきましたが、最も失敗した定義の使用でさえ、私たちが見ているものよりも優れています。



さらに、使用されるデータは単にマジック定数の形式ではなく、明らかに変換された定数、つまりビットマスクからの反転です。 私がそのような解決策を支持して発明できる唯一の議論は、コンパイル時間の短縮ですが、この仮定は、理論的な目的のためだけに与えられるほど無視できるほど重要ではないと思われます



ええ、はい、私は定数サポーターのもう1つの議論を忘れました-名前付き定数はコンパイラ名テーブルを混乱させ、コンパイル中にRAMサイズの要件を増やします。 私は笑わず、ある種の正気な人からそのような議論を本当に聞きました、そしてそれは微妙なトローリングであることが判明しました(しかし、それは私に数十の不快な秒をもたらしました-私は本当に人を間違えるかもしれません)。



この時点で、そのようなコードの作成者を正当化しようとするのをやめ(はい、私は実際には試しませんでした)、逐次近似の方法を使用して通常のコードに移動します。

 #define CsrReg *(unsigned int *)0xf80ff000 //  , . 127 #define CsrRegReady 0x1000 //   CsrReg &= ~CsrRegReady;
      
      



すでに良く見えており、途中で止まる人もいますが、道路の真ん中にはいません。 このフラグメントの何が問題になっていますか? まず第一に、あなたが幸せな人であれば、直接反転操作、それからあなたは書くときにそれを見逃すことはありません。 私はそれほど幸せではないので(おそらく、あなたのように注意を払って収集されているわけではありません)、チルドをスキップして、エラーの場所を探してコードを実行していました。



したがって、私たちのマクロはすべてです(あなたがプロに切り替えた場合、まあ、またはインライン関数)

 #define RegBitClr(REG,MASK) (REG) &= ~(MASK) RegBitClr(CsrReg,CsrRegReady);
      
      



(マクロ名は大文字で書かなければならないことを知っており、この声明を支持する議論さえ理解していますが、私は受け入れません-私は芸術家です、私はそう思います)。 ここで何が間違っているのかを理解するために、次のフラグメントの最適化が有効になっている生成コードを見るといいでしょう

  RegBitClr(CsrReg,CsrRegReady); RegBitClr(CsrReg,~CsrRegReady); //    
      
      



最初の行は無視され、2番目の行は式CSrReg = 0に相当するコードを生成します。 実際、コンパイラーの観点から、最初の行では12を除くすべてのビットをリセットし、2番目の行ではそれもリセットします。つまり、ゼロになります。



しかし、実際には、これは完全には明らかではありません。レジスタを扱っているためです。つまり、変数の変数の性質をコンパイラに直接示す必要があり、
 #define CsrAdr (unsigned int *)0xf80ff000 volatile unsigned int * const CsrReg = CsrAdr; #define RegBitClr(REG,MASK) *(REG) &= ~(MASK) RegBitClr(CsrReg,CsrRegReady);
      
      



ここでは、すべてがすでにほとんど良好です。このオプションでは、書き込みができないことに注意してください。
  RegBitClr(CsrRegReady,CsrReg);
      
      



、コンパイラが私たちをscるので。 もちろん、私たちはそのように書くべきではありません、これは間違った表現ですが、この投稿は間違いを犯しがちな普通の人、間違いを犯さないプログラミングの半神が好きなことをして、良いスタイルのような退屈なことを考えない人を対象としていますコンパイラによる型チェック。 (「平凡だけが秩序を必要とする。天才はカオスを支配する。」)



あまり成功しなかったものは何ですか? のようなものを書く能力
  RegBitClr(CsrReg,3);
      
      



というのは、マジック定数を避けるべきだということで合意したからです。コンパイラーが私たちをヤンクしたほうがいいのですが、残念ながら、これはCでは達成できません。関数に関するマクロの欠点の1つを思い出してみましょう-パラメーターをチェックしません。 インラインはCのコンパイラディレクティブではないため(実際、少なくともIARのバージョンでは同じですが、プラグマ(松葉杖、あらゆる場所で松葉杖)の助けを借りて勝ちましたが)、実際の関数を呼び出すことはできません。ビットをリセットすると非常に高価になります
 typedef volatile unsigned int * const Reg_t; enum CsrRegBits {CsrRegReady=0x1000,CsrRegDone=0x100}; inline void RegBitClr(Reg_t Reg, const enum CsrBits Mask) { *Reg &= ~Mask; }; RegBitClr(CsrReg,CsrRegReady); RegBitClr(CsrReg,~CsrRegReady); //   RegBitClr(CsrReg,3); //   
      
      



ほぼ完璧なソリューションになります。



確かに、あなたは書くことができず、
  RegBitClrf(CsrReg,CsrRegReady | CsrRegDone); //     RegBitClrf(CsrReg,(enum CsrRegBits)(CsrRegReady | CsrRegDone)); //    
      
      



もちろん、最後の行では受け入れられない式を作成できますが、少なくとも、ロープの長さに慣れていることを明示的に示しています。 少なくともIARの場合、このようなソリューションに推奨する唯一のことは、「すべての警告をエラーとして扱う」ボックスをオンにして、制御を強化することです。



さて、結論-これまでのところ、単一タスクの中断のないプログラムのみを検討してきました。 中断の可能性を考慮する必要があると想像してください。マクロまたは関数のテキストに2行追加することでこれを行いますが、元のフラグメントの作成者が何をするか想像するのは困難です。



重要な追加-コメントでは、Habrユーザーの1人、つまりpwlは、型チェックの問題に対する完全に魅力的なソリューションを提供しました。つまり、マクロ引数を使用してマクロにダミー関数を追加し、コンパイラが型をチェックして警告を発行しますが、関数呼び出しは抑制されます。結果は定数論理式に関与します。 つまり、両方のオオカミはうんざりし(タイプを確認しました)、羊は無傷です(結果のコードは成長していません)。 どうもありがとうございました。これはまさに私が実りある議論と呼んでいるものであり、私自身がそのようなことを考えなかったのは残念です。 これは完璧なソリューションです。

 typedef volatile unsigned int * const Reg_t; enum CsrRegBits {CsrRegReady=0x1000,CsrRegDone=0x100}; void TestBitClrArg(Reg_t adr, enum CsrRegBit data); //   ,      #define RegBitClr(REG,MASK) (*(REG) &= ~(MASK), 1||TestBitClrArg(REG,BIT)) RegBitClr(CsrReg,CsrRegReady); RegBitClr(CsrReg,~CsrRegReady); //   RegBitClr(CsrReg,3); //   
      
      






All Articles