埋め込みコードの場合、「unsigned int」ではなく「uint_t」タイプを使用する必要があるのはなぜですか?


22

gccを使用して、STM32F105用のアプリケーションをcで作成しています。

(単純なプロジェクトで)過去には、私はいつものように、変数定義されているcharintunsigned int、などを。

私は、次のような、stdint.hで定義された型を使用するのが一般的であることがわかりint8_tuint8_tuint32_tそれは私が使用していることを複数のAPIのでは、ともSTからARM CMSISライブラリにtrueこの、など。

なぜそうするべきかを理解していると思います。コンパイラがメモリ空間をより最適化できるようにします。他にも理由があると思います。

ただし、cの整数プロモーションルールのため、2つの値を追加したり、ビットごとの演算を実行したりするたびに、変換警告に対して実行し続けますconversion to 'uint16_t' from 'int' may alter its value [-Wconversion]。この問題については、ここここで説明します

intまたはとして宣言された変数を使用する場合は発生しませんunsigned int

これを考えて、いくつかの例を挙げます。

uint16_t value16;
uint8_t value8;

これを変更する必要があります。

value16 <<= 8;
value8 += 2;

これに:

value16 = (uint16_t)(value16 << 8);
value8 = (uint8_t)(value8 + 2);

いですが、必要に応じてできます。私の質問は次のとおりです。

  1. 変換する場合があり、符号なし署名したとするバック符号なしの結果が不正確になりますか?

  2. stdint.h整数型を使用する/使用しない他の大きな理由はありますか?

私が受け取った答えに基づいて、cが変換uintint、戻っても、stdint.hタイプが一般的に好まれているようです。これはより大きな質問につながります:

  1. 型キャスト(例value16 = (uint16_t)(value16 << 8);)を使用して、コンパイラの警告を防ぐことができます。問題を隠しているだけですか?それについてもっと良い方法はありますか?

符号なしリテラルを使用します:ie 8uおよび2u
ハーミングモニカを停止14

ありがとう、@ OrangeDog、私は誤解していると思う。私は両方を試してみましたvalue8 += 2u;value8 = value8 + 2u;、私は同じ警告を取得します。
ビットスマック

とにかくそれらを使用して、まだ幅の警告がない場合に署名された警告を避けるために:)
停止Harming Monica 14

回答:


11

int17〜32ビットのどこかにあった標準に準拠したコンパイラは、次のコードで必要なことを正当に行うことができます。

uint16_t x = 46341;
uint32_t y = x*x; // temp result is signed int, which can't hold 2147488281

そのようにしたい実装は、考えられるすべてのプロトコルを使用して、すべてのポートピンで文字列「Fred」を繰り返し出力する以外は何もしないプログラムを合法的に生成できます。プログラムがそのようなことを行う実装に移植される可能性は非常に低いですが、理論的には可能です。未定義の動作に関与しないことが保証されるように上記のコードを記述したい場合、後者の式を(uint32_t)x*xor として記述する必要があります1u*x*xint17〜31ビットのコンパイラでは、後者の式は上位ビットを切り捨てますが、未定義の動作には関与しません。

gccの警告はおそらく、記述されたコードが完全に100%移植可能ではないことを示唆しようとしていると思います。一部の実装では未定義になる動作を回避するためにコードを実際に記述する必要がある場合がありますが、他の多くの場合、過度に迷惑なことを行う実装ではコードが使用されそうにないことを単に理解する必要があります。

intおよびなどのタイプを使用shortすると、一部の警告が削除され、一部の問題が修正される場合がありますが、他の問題が発生する可能性が高いことに注意してください。uint16_tとCの整数プロモーションルールのような型の相互作用は厄介ですが、そのような型はおそらく他のどの型よりも優れています。


6

1)間に何の操作も行わずに、同じ長さの符号なし整数から符号付き整数にキャストした場合、毎回同じ結果が得られるため、ここでは問題ありません。ただし、さまざまな論理演算および算術演算は、符号付きオペランドと符号なしオペランドで異なる動作をしています。
2)使用する主な理由stdint.hのタイプは、Aタイプのビットサイズが定義さのために真でないプラットフォームのすべてにわたって同じであるということであるintlongなど、ならびにchar標準的なsignessは、それが署名されないかによって、符号なしすることができましたデフォルト。余分なチェックと仮定を使用せずに、正確なサイズを知っているデータを簡単に操作できます。


2
のサイズint32_tとサイズは、それらが定義されているuint32_tすべてのプラットフォーム同じです。プロセッサに完全に一致するハードウェアタイプがない場合、これらのタイプは定義されません。したがってintなどなどの利点、そしておそらく、int_least32_tなど
ピートベッカー

1
@PeteBecker-コンパイルエラーが発生すると、問題をすぐに認識できるため、これは間違いなく利点です。私のタイプが私のサイズを変更するよりも、私はむしろそうです。
サピ14

@sapi-多くの場合、基礎となるサイズは無関係です。Cプログラマーは、長年にわたってサイズを固定しなくてもうまくいきました。
ピートベッカー14

6

Eugeneの#2がおそらく最も重要なポイントであるため、私はそれが

MISRA (directive 4.6): "typedefs that indicate size and signedness should be used in place of the basic types".

また、Jack Ganssleはその規則を支持しているようです:http ://www.ganssle.com/tem/tem265.html


2
「同じサイズの結果を得るために他の同じサイズの整数と安全に乗算できるNビットの符号なし整数」を指定するタイプがないのは残念です。整数の昇格ルールは、などの既存の型と恐ろしく相互作用しuint32_tます。
supercat 14

3

警告を削除する簡単な方法は、GCCで-Wconversionを使用しないことです。このオプションを手動で有効にする必要があると思いますが、そうでない場合は、-Wno-conversionを使用して無効にすることができます。まだ必要な場合は、他のオプションを使用して、符号およびFP精度変換の警告を有効にできます。

-Wconversionの警告はほとんど常に偽陽性です。これは、おそらく-Wextraでさえデフォルトで有効にしない理由です。スタックオーバーフローの問題は良いオプションセットのための提案をたくさん持っています。私自身の経験に基づいて、これは始めるのに適した場所です。

-std = c99 -pedantic -Wall -Wextra -Wshadow

必要な場合は追加しますが、必要ない場合があります。

-Wconversionを保持する必要がある場合は、数値オペランドを型キャストするだけでコードを少し短くできます。

value16 <<= (uint16_t)8;
value8 += (uint8_t)2;

ただし、構文を強調せずに読むのは簡単ではありません。


2

ソフトウェアプロジェクトでは、移植可能な型定義を使用することが非常に重要です。(同じコンパイラーの次のバージョンでもこの考慮が必要です。)良い例で、数年前、現在のコンパイラーが 'int'を8ビットとして定義したプロジェクトに取り組みました。コンパイラの次のバージョンでは、「int」を16ビットとして定義しました。'int'に移植可能な定義を使用していなかったため、RAMは(事実上)サイズが2倍になり、8ビットintに依存する多くのコードシーケンスが失敗しました。移植可能な型定義を使用すると、その問題(修正に数百人時間)が回避されます。


int8ビット型を参照するために使用する合理的なコードはありません。CCSのような非Cコンパイラがそうする場合でも、妥当なコードはchar、8ビットにはtypedefedタイプ、16ビットにはtypedefedタイプ(「long」ではない)を使用する必要があります。一方で、CCSのようなものから実際のコンパイラーへのコードの移植は、適切なtypedefを使用している場合でも問題になりやすいです。
supercat

1
  1. はい。nビットの符号付き整数は、nビットの符号なし整数として非負数の約半分の数を表すことができ、オーバーフロー特性に依存することは未定義の動作なので、何でも起こります。現在および過去のプロセッサの大半は2の補数を使用しているため、符号付き整数型と符号なし整数型で多くの演算が同じことを行いますが、それでもすべての演算がビット単位で同一の結果を生成するわけではありません。コードが意図したとおりに動作しない理由がわからない場合、後で余分なトラブルを本当に求めています。

  2. 一方でint型符号なしの実装定義された大きさを持って、これらは多くの場合、サイズや速度の理由でいずれかの実装により、「スマート」に選ばれます。他のことをする正当な理由がない限り、私はこれらに固執します。同様に、intを使用するか符号なしを使用するかを検討するときは、特に理由がない限り、intを好む。

型のサイズまたは符号付きをより適切に制御する必要がある場合、通常、システム定義のtypedef(size_t、intmax_tなど)を使用するか、特定の関数を示す独自のtypedefを作成することを好みます。タイプ(prng_int、adc_intなど)。


0

多くの場合、コードはARMサムおよびAVR(およびx86、powerPCおよびその他のアーキテクチャ)で使用され、8ビットに収まる変数であっても、STM32 ARMでは16ビットまたは32ビットがより効率的です(両方の方法:フラッシュとサイクル)AVRでは8ビットがより効率的です。ただし、SRAMがほぼ満杯の場合、グローバル変数の場合は8ビットに戻すのが妥当です(ただしローカル変数の場合はそうではありません)。移植性と保守(特に8ビット変数の場合)には、1つの.hの場所(通常はifdefの下)で正確なサイズとtypedefの代わりに、MINIMUMの適切なサイズ指定して(おそらくuint_fast8_t / uint_least8_t)移植/ビルド時、例えば:

// apparently uint16_t is just as efficient as 32 bit on STM32, but 8 bit is punished (with more flash and cycles)
typedef uint16_t uintG8_t; // 8bit if SRAM is scarce (use fol global vars that fit in 8 bit)
typedef uint16_t uintL8_t; // 8bit on AVR (local var, 16 or 32 bit is more efficient on STM + less flash)
// might better reserve 32 bits on some arch, STM32 seems efficient with 16 bits:
typedef uint16_t uintG16_t; // 16bit if SRAM is scarce (use fol global vars that fit in 16 bit)
typedef uint16_t uintL16_t; // 16bit on AVR (local var, 16 or 32 bit whichever is more efficient on other arch)

GNUライブラリは少し役立ちますが、通常typetypeはとにかく意味があります:

typedef uint_least8_t uintG8_t;
typedef uint_fast8_t uintL8_t;

//ただし、SRAMが問題にならない場合は両方ともuint_fast8_tです。

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