예를 들어 avr-gcc를 사용하면 int 유형의 너비는 16 비트로 지정됩니다. C에서 8 비트 피연산자에 대한 연산을 수행하면 C의 정수 승격으로 인해 피연산자가 16 비트 int 유형으로 변환됩니다. 이는 C로 작성된 경우 AVR의 모든 8 비트 산술 연산이 훨씬 오래 걸리는 것을 의미합니다. C의 정수 승격으로 인해 어셈블리로 작성된 경우?
예를 들어 avr-gcc를 사용하면 int 유형의 너비는 16 비트로 지정됩니다. C에서 8 비트 피연산자에 대한 연산을 수행하면 C의 정수 승격으로 인해 피연산자가 16 비트 int 유형으로 변환됩니다. 이는 C로 작성된 경우 AVR의 모든 8 비트 산술 연산이 훨씬 오래 걸리는 것을 의미합니다. 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은 모두 정수이므로 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 수학을하라고 말했습니다.
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 비트 비용이 더 많이 든다. 나는 조금 / 많은 것을 속였고, 공정한 방식으로 더 많은 것을보기 위해서는 약간 더 복잡한 예제가 필요할 것입니다.
의견 토론을 바탕으로 편집
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를 사용할 수 없습니까? (나는 그것을 물었을 때 답을 알고 있었다).
편집 2
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.
분명히 최적화는 내 출력과 어떻게 비교되는지 확인하기 위해 자신의 컴파일러로 시도하는 데 1 초 밖에 걸리지 않습니다.
whatever-gcc -O2 -c so.c -o so.o
whatever-objdump -D so.o
그리고 예 avr, pic 등의 바이트 크기 변수에 바이트를 사용하면 메모리가 절약되고 실제로 절약하려고합니다 ... 실제로 사용하고 있지만 가능한 한 여기에 표시된 것처럼 가능한 한 많은 레지스터에 메모리에 들어가므로 플래시 절약 효과는 추가 변수가 없기 때문에 발생합니다.
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 바이트로 맞추기위한 pragma 또는 명령 줄 스위치 가 있습니다.
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...