8 비트 MCU에 대한 C 정수 프로모션


14

예를 들어 avr-gcc를 사용하면 int 유형의 너비는 16 비트로 지정됩니다. C에서 8 비트 피연산자에 대한 연산을 수행하면 C의 정수 승격으로 인해 피연산자가 16 비트 int 유형으로 변환됩니다. 이는 C로 작성된 경우 AVR의 모든 8 비트 산술 연산이 훨씬 오래 걸리는 것을 의미합니다. C의 정수 승격으로 인해 어셈블리로 작성된 경우?


1
그렇게 생각하지 않습니다. 컴파일러는 대상 변수가 (서명되지 않은) 문자임을 인식하므로 상위 8 비트를 계산하는 데 방해가되지 않습니다. 그래도 GCC가 코드 최적화에 좋지 않은 경우가 있으므로 ASM으로 코딩하면 MGIHT 결과가 더 빠릅니다. 그러나 예산 제약이 매우 높은 매우 중요한 작업 / 인터럽트를 수행하지 않는 한 더 강력한 프로세서를 선택하고 C로 프로그래밍하거나 성능 저하에 대해 걱정하지 마십시오 (시간 대신 고려) 시장 출시, 더 나은 코드 가독성 / 재사용 성, 버그 수 감소 등).
next-hack

확인할 시간이 없어서 죄송합니다. 그러나 gcc에는 '정수 승격'을 제어하는 ​​명령 줄 플래그가 있다고 생각합니다. 특정 코드 조각에 대해 제어하는 ​​pragma도 있습니다. 성능이 얼마나 중요합니까? AVR을 많이 사용하는 경우 일부 산술의 속도 차이는 문제가되지 않습니다. Foxus가 코드를 올바르게 작동시키는 방법 그런 다음 성능 문제가 있으면 무엇이 문제인지 확인하십시오. 어셈블러에서 시간 코딩을 낭비하는 것은 쉬울 것입니다.
gbulmer

1
컴파일러를 분해하고 수행중인 작업을 확인하십시오. 순수한 언어 관점에서 그렇습니다. 여기서 구현은 비정형입니다. 일반적으로 int는 레지스터 크기에 맞추려고 시도하며 16 비트 레지스터가 있으면 8 비트 수학은 실제로 8보다 16 비트가 저렴합니다. 그러나 이것은 다른 방법이며 8 비트 MCU를 사용하면 int를 구현하는 것이 좋습니다 16 비트로 그래서 당신은 아마 당신이 이것에 관심이있는 곳에서 uchar를 사용해야하지만 다른 곳에서 가장 많이 아프기 때문에 일반적인 프로그래밍 습관을 만들지 마십시오.
old_timer

3
기억하십시오 : 의견에 질문에 대답하지 마십시오.
파이프

4
이러한 종류의 질문은 순수한 소프트웨어 질문이므로 SO의 C 전문가에게 문의하는 것이 좋습니다. C에서의 정수 승격은 다소 복잡한 주제입니다. 평균 C 프로그래머는 그것에 대해 많은 오해를 가질 것입니다.
Lundin

답변:


16

간단히 말해 :

16 비트의 정수 승격이 항상 발생합니다. C 표준이이를 시행합니다. 그러나 컴파일러는 유형이 승격 된 경우와 부호가 같을 것으로 추정 할 수있는 경우 계산을 8 비트로 다시 최적화 할 수 있습니다 (내장 시스템 컴파일러는 일반적으로 이러한 최적화에 매우 적합합니다) .

항상 그런 것은 아닙니다! 정수 승격으로 인한 암시 적 서명 변경은 임베디드 시스템의 일반적인 버그 소스입니다.

자세한 설명은 암시 적 유형 승격 규칙 에서 찾을 수 있습니다 .


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 등의 바이트 크기 변수에 바이트를 사용하면 메모리가 절약되고 실제로 절약하려고합니다 ... 실제로 사용하고 있지만 가능한 한 여기에 표시된 것처럼 가능한 한 많은 레지스터에 메모리에 들어가므로 플래시 절약 효과는 추가 변수가 없기 때문에 발생합니다.


2
"컴파일러는이 작업을 시작한 버전이 무엇인지 잘 모르고, 경력 초기에이 문제에 부딪 쳤으며, 컴파일러가 위와 같이 순서가 틀린 프로모션에도 불구하고, uchar 수학을하도록 지시했지만 프로모션을 수행했지만, 놀라지 않았습니다. " 임베디드 시스템의 C 컴파일러는 컴파일러는 일반적으로 최적화 할 수 있습니다 :) 끔찍한 표준 적합성을 가지고 사용되기 때문에,하지만, 여기에는 결과가에 적합 할 때 공제 할 수 unsigned char는 있도록 16 비트에 대한 홍보를 수행하기 위해 필요에 따라, 표준에 의해.
Lundin

1
@old_timer (a<<8)|bint16 비트 인 모든 시스템에서 항상 잘못되었습니다 . 서명 된 것으로 a암시 적으로 승격됩니다 int. aMSB에 값을 보유한 경우 해당 데이터를 16 비트 숫자의 부호 비트로 이동하면 정의되지 않은 동작이 발생합니다.
Lundin

1
fun3은 fun..ny ... 컴파일러에 의해 완전히 최적화되지 않았습니다 ... rCC가 GCC에서 항상 0이고 변수 a, b 및 결과에 대한 레지스터를 ra, rb, {rh, rl}로 간주하고, 컴파일러는 다음을 수행 할 수 있습니다. 1) mov rh, r1; 2) mov rl, ra; 2) rl, rb를 추가; 3) adc rh, rh; 4) ret. 4 명령어, vs 7 또는 8 ... 명령어 1은 ldi rh, 0에서 변경할 수 있습니다.
next-hack

1
컴파일러와 사용중인 관련 옵션을 지정하면 더 나은 대답입니다.
Russell Borogove

1
int / char 등을 사용하지 말고 훨씬 더 명확하고 읽기 쉬운 int16_t 및 int8_t를 사용하는 것이 좋습니다.
사용자

7

현대 컴파일러가 생성 된 코드를 최적화하는 데 훌륭한 역할을하기 때문에 반드시 그런 것은 아닙니다. 예를 들어, z = x + y;모든 변수가있는 곳에 쓰면 계산을 수행 unsigned char하기 unsigned int전에 컴파일러가 변수 를 승격시켜야합니다 . 그러나 승격없이 최종 결과는 정확히 동일하므로 컴파일러는 8 비트 변수 만 추가하는 코드를 생성합니다.

물론, 항상 그런 것은 아닙니다. 예를 들어 z = (x + y)/2; 는 상위 바이트에 의존하므로 승격이 진행됩니다. 중간 결과를로 다시 캐스팅하여 어셈블리에 의존하지 않고 피할 수 있습니다 unsigned char.

컴파일러 옵션을 사용하면 이러한 비 효율성을 피할 수 있습니다. 예를 들어, 많은 8 비트 컴파일러에는 intC에서 요구하는 대신 열거 유형을 1 바이트로 맞추기위한 pragma 또는 명령 줄 스위치 가 있습니다.


4
"컴파일러는 unsigned int로 승격시켜야합니다." 아니요, 컴파일러는로 승격해야 합니다. 플랫폼 과 동일한 전환 순위를 가지지 않기 int때문 char입니다 int.
Lundin

3
예를 들어, 많은 8 비트 컴파일러에는 C에 필요한 int가 아닌 1 바이트의 열거 형에 맞는 pragma 또는 명령 줄 스위치가 있습니다. " C 표준에서는 열거 변수를 1 바이트로 할당 할 수 있습니다. 그것은 단지 것을 요구 열거 상수가 있어야합니다 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...
Lundin
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.