例としてavr-gccを使用すると、int型は16ビット幅に指定されます。Cの8ビットオペランドで演算を実行すると、Cでの整数の昇格により、それらのオペランドが16ビットint型に変換されます。これは、AVRでの8ビット算術演算はすべて、C Cの整数昇格のためにアセンブリで記述されている場合
例としてavr-gccを使用すると、int型は16ビット幅に指定されます。Cの8ビットオペランドで演算を実行すると、Cでの整数の昇格により、それらのオペランドが16ビットint型に変換されます。これは、AVRでの8ビット算術演算はすべて、C Cの整数昇格のためにアセンブリで記述されている場合
回答:
短い話:
16ビットへの整数の昇格は常に行われます-C標準はこれを実施します。しかし、コンパイラーは、型が昇格された場合と同じ符号になると推測できる場合、計算を8ビットまで最適化して戻すことができます(組み込みシステムコンパイラーは通常、このような最適化にかなり適しています)。
これは常にそうではありません!整数の昇格によって引き起こされる暗黙的な署名の変更は、組み込みシステムのバグの一般的な原因です。
詳細な説明は、暗黙的なタイププロモーションルールにあります。
unsigned int fun1 ( unsigned int a, unsigned int b )
{
return(a+b);
}
unsigned char fun2 ( unsigned int a, unsigned int b )
{
return(a+b);
}
unsigned int fun3 ( unsigned char a, unsigned char b )
{
return(a+b);
}
unsigned char fun4 ( unsigned char a, unsigned char b )
{
return(a+b);
}
予想どおりfun1はすべてintなので、16ビットの数学も同様です
00000000 <fun1>:
0: 86 0f add r24, r22
2: 97 1f adc r25, r23
4: 08 95 ret
コードによって呼び出される16ビットの追加であるため、技術的には正しくありませんが、最適化されていない場合でも、結果のサイズのためにこのコンパイラはadcを削除しました。
00000006 <fun2>:
6: 86 0f add r24, r22
8: 08 95 ret
ここでプロモーションが実際に驚くことはありませんでしたが、コンパイラはこれを実行していませんでした驚くことではなく、uchar mathを実行するように言った
0000000a <fun3>:
a: 70 e0 ldi r23, 0x00 ; 0
c: 26 2f mov r18, r22
e: 37 2f mov r19, r23
10: 28 0f add r18, r24
12: 31 1d adc r19, r1
14: 82 2f mov r24, r18
16: 93 2f mov r25, r19
18: 08 95 ret
そして理想は、8ビットであることを知っており、8ビットの結果が欲しいので、8ビットを最後までやり遂げるように言っただけです。
0000001a <fun4>:
1a: 86 0f add r24, r22
1c: 08 95 ret
そのため、一般的に、コンパイラーの作成者が妥協しなければならなかったこのような8ビットmcuの場合、理想的には(u)intのサイズであるレジスターのサイズを目指した方が良いです... 8ビット以上を必要としない数学にucharを使用する場合、コードを移動したり、より大きなレジスタを備えたプロセッサでそのような新しいコードを記述したりすると、コンパイラーはマスキングと符号拡張を開始する必要があります。その他はいけない。
00000000 <fun1>:
0: e0800001 add r0, r0, r1
4: e12fff1e bx lr
00000008 <fun2>:
8: e0800001 add r0, r0, r1
c: e20000ff and r0, r0, #255 ; 0xff
10: e12fff1e bx lr
8ビットのコストを強制します。私は少し/多くをcheしましたが、これをもっと公平に見るには少し複雑な例を必要とします。
コメントディスカッションに基づく編集
unsigned int fun ( unsigned char a, unsigned char b )
{
unsigned int c;
c = (a<<8)|b;
return(c);
}
00000000 <fun>:
0: 70 e0 ldi r23, 0x00 ; 0
2: 26 2f mov r18, r22
4: 37 2f mov r19, r23
6: 38 2b or r19, r24
8: 82 2f mov r24, r18
a: 93 2f mov r25, r19
c: 08 95 ret
00000000 <fun>:
0: e1810400 orr r0, r1, r0, lsl #8
4: e12fff1e bx lr
驚きません。なぜオプティマイザーは余分な命令を残したのに、r19でldiを使用できないのですか?(私はそれを尋ねたときに答えを知っていた)。
EDIT2
AVR用
avr-gcc --version
avr-gcc (GCC) 4.9.2
Copyright (C) 2014 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
悪い習慣を避けるため、または8ビットの比較ではありません
arm-none-eabi-gcc --version
arm-none-eabi-gcc (GCC) 7.2.0
Copyright (C) 2017 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
明らかに、最適化がオンになっているのは、自分のコンパイラと比較して、それが私の出力とどのように比較されるかを確認するのに数秒しかかかりませんが、とにかく:
whatever-gcc -O2 -c so.c -o so.o
whatever-objdump -D so.o
はい、確かにavr、picなどで、バイトサイズの変数にバイトを使用すると、メモリを節約できます。実際に使用する場合は、実際にそれを使用する必要があります。できるだけ多くのレジスタにメモリ内に配置するため、追加の変数がないことでフラッシュの節約が実現します。RAMの節約は実際の場合とそうでない場合があります。
unsigned char
、それはので、持っている 16ビットへのプロモーションを行うために必要に応じて、標準で。
(a<<8)|b
は、int
16ビットのシステムでは常に間違っています。a
にint
署名されている暗黙的に昇格されます。場合にはa
MSBの値を保持は、未定義の動作を呼び出す16ビット数の符号ビットにそのデータをシフトしてしまいます。
必ずしもではありません。現代のコンパイラーは、生成されたコードの最適化に優れた仕事をしているからです。例えばz = x + y;
、すべての変数がunsigned char
である場所を記述する場合unsigned int
、計算を実行する前にそれらをプロモートする必要があります。ただし、最終結果は昇格なしではまったく同じになるため、コンパイラは8ビット変数を追加するだけのコードを生成します。
もちろん、これは常に当てはまるわけではありません。たとえば、の結果はz = (x + y)/2;
上位バイトに依存するため、昇格が行われます。中間結果をにキャストすることにより、アセンブリに頼らずに回避できunsigned char
ます。
このような非効率性の一部は、コンパイラオプションを使用して回避できます。たとえば、多くの8ビットコンパイラには、int
Cで必要な代わりに列挙型を1バイトに収めるためのプラグマまたはコマンドラインスイッチがあります。
int
ことから、char
最も可能性が高いと同じ変換ランクを持っていないであろうint
任意のプラットフォーム上。
int
(はい、一貫性がありません)。C11 6.7.2.2Each enumerated type shall be compatible with char, a signed integer type, or an unsigned integer type. The choice of type is implementation-defined...