C에서 조립까지


16

avr-8bit에 대해 다음과 같은 C 코드가 있다고 가정합니다.

int v1=1;
int v2=2;
v2=v2+v1;

다음과 같은 분해가 예상되었습니다.

ldi r18, 1;
ldi r19, 2;
add r19, r18;

그러나 내가 달린 후에 :

avr-gcc -mmcu=atmega2560 Test.c -o Test.elf

avr-objdump -S Test.elf > Test.lss

나는 다음과 같은 분해를 얻었다

    ldi r24, 0x01   ; 1
    ldi r25, 0x00   ; 0
    std Y+2, r25    ; 0x02
    std Y+1, r24    ; 0x01
    ldi r24, 0x02   ; 2
    ldi r25, 0x00   ; 0
    std Y+4, r25    ; 0x04
    std Y+3, r24    ; 0x03
    ldd r18, Y+3    ; 0x03
    ldd r19, Y+4    ; 0x04
    ldd r24, Y+1    ; 0x01
    ldd r25, Y+2    ; 0x02
    add r24, r18
    adc r25, r19
    std Y+4, r25    ; 0x04
    std Y+3, r24    ; 0x03

분해기의 결과를 이해하는 데 도움이 될 사람이 있습니까?

편집 : char을 사용하여 어셈블리는 다음과 같습니다.

ldi r24, 0x01
std Y+1, r24
ldi r24, 0x02
std Y+2, r24
ldd r25, Y+2
ldd r24, Y+1
add r24, r25
std Y+2, r24

표준 교육은 언제입니까?

답변:


20

짧은 대답 : 레지스터는 8 비트이고 값은 16 비트입니다. 따라서 두 조각으로 처리합니다.

긴 대답 :

    ldi r24, 0x01   ; 1
    ldi r25, 0x00   ; 0

16 비트 값 1을 8 비트 레지스터 r24, r25에 저장하십시오.

    std Y+2, r25    ; 0x02
    std Y+1, r24    ; 0x01

스택 위치 Y + 1, Y + 2에 보관하십시오.

    ldi r24, 0x02   ; 2
    ldi r25, 0x00   ; 0

16 비트 값 2를 8 비트 레지스터 r24, r25에 저장하십시오.

    std Y+4, r25    ; 0x04
    std Y+3, r24    ; 0x03

스택 위치 Y + 3, Y + 4에 보관하십시오.

    ldd r18, Y+3    ; 0x03
    ldd r19, Y+4    ; 0x04
    ldd r24, Y+1    ; 0x01
    ldd r25, Y+2    ; 0x02

스택에서 다시 (r18, r19) 및 (r24, r25)로 복사하십시오.

    add r24, r18
    adc r25, r19

두 번째 추가시 캐리를 포함하여 (r18, r19)를 (r24, r25)에 추가

    std Y+4, r25    ; 0x04
    std Y+3, r24    ; 0x03

다시 스택에 보관하십시오.

원래 어셈블리를 얻으려면 다음 두 가지를 시도하십시오.

  • "char"변수를 사용하십시오
  • "-O2"컴파일러 옵션 사용

편집 : 컴파일러가 변수를 레지스터에 유지하지 않고 스택에 저장하는 이유는 기본 "자동"스토리지 유형으로 저장되기 때문입니다. 그것은 레지스터로 최적화 그러나 당신이 그들을 선언 할 경우에도 "등록"스토리지 클래스에 없습니다.

이것은 언어의 엄격한 요구 사항은 아니지만 일반적인 컴파일러 동작입니다. 어떤 시점에서 v1의 주소를 가져 오면 저장 위치를 ​​지정하고 "v1"값이 변경 될 때마다 다시 저장 해야합니다 . 따라서 v1을 레지스터 또는 스택에 저장해야하는지 여부에 대한 부기를 저장하기 위해 스택에 저장하고 각 코드 행을 개별적으로 처리합니다.


감사합니다! 지금은 더 명확합니다! 질문에서 편집 한 내용을 찾으십시오.
DarkCoffee

1
내 편집을 참조하십시오. -O2도 시도하십시오. 코드가 깨질 수 있지만 -O3 일 수 있습니다.
pjc50

3
내가 사용하는 많은 임베디드 코드는 예를 들어 부호없는 정수에 대한 "uint8, uint16, uint32"와 같이 크기에 특정한 추가 유형을 정의합니다. 그렇게하면 항상 어떤 종류의 변수를 다루는 지 정확하게 알 수 있습니다. 특히 정의되지 않은 크기 / 서명의 작은 임베디드, 부호있는 플로트 "int"의 경우 CPU 사이클을 최고로 소모하고 최악의 경우 심각한 버그가 발생합니다.
John U

실제 컴파일러는 약 10-15 년 전에 이와 같이 동작을 멈췄습니다. 레지스터 할당 문제는 대부분 해결되었으며 컴파일러는 능숙합니다. 변수가 스택에 있어야하는 시점과 레지스터에있을 수있는 시점, 이동하려는 노력이 필요한지 여부 및시기를 정확히 알고 있습니다. 부기는 컴파일 타임에 수행되며 컴파일러 자체에는 기가 바이트의 메모리가 있습니다. 큰 예외는 명백한 이유로 디버그 모드이지만 모든 것이 스택에 있습니다.
MSalters

@ pjc50 -O3은 깨진 코드를 생성 할 수 있습니까? [citation needed] (그리고 정의되지 않은 행동을 불러 온 다음 최적화 설정으로 중단되는 C 코드는 계산되지 않습니다)
marcelm

4

예제 코드를 찾았을 때 내 의견에 답을 드리겠습니다. 다른 사람들은 이미 문제를 설명했습니다.

내가 사용하는 많은 임베디드 코드는 예를 들어 부호없는 정수에 대한 "uint8, uint16, uint32"와 같이 크기에 특정한 추가 유형을 정의합니다. 그렇게하면 항상 어떤 종류의 변수를 다루는 지 정확하게 알 수 있습니다. 특히 크기가 작거나 정의되지 않은 작은 임베디드, 부호있는 플로트 "int"의 경우 CPU 사이클을 최고로 소모하고 최악의 경우 심각한 버그가 발생합니다.

다음은 현재 #defines입니다.

/*
 * Example - the basic data types from our embedded code
 */
typedef unsigned char       uint8;  /*  8 bits */
typedef unsigned short int  uint16; /* 16 bits */
typedef unsigned long int   uint32; /* 32 bits */

typedef char                int8;   /*  8 bits */
typedef short int           int16;  /* 16 bits */
typedef int                 int32;  /* 32 bits */

typedef volatile int8       vint8;  /*  8 bits */
typedef volatile int16      vint16; /* 16 bits */
typedef volatile int32      vint32; /* 32 bits */

typedef volatile uint8      vuint8;  /*  8 bits */
typedef volatile uint16     vuint16; /* 16 bits */
typedef volatile uint32     vuint32; /* 32 bits */

3
좋은 생각; uint8_t와 친구는 이제 표준의 일부입니다 : stackoverflow.com/questions/16937459/…
pjc50

얼마나 편리합니다! 우리는 C89라는 프로젝트를 가진 사람들을 물려 받았으므로 공식 버전이 있다는 것을 아는 것이 좋습니다.
John U

2

C 코드는 16 비트 정수 변수 (int)를 사용합니다. 컴파일러는 마음을 읽을 수 없으므로 소스 파일에있는 내용을 정확하게 컴파일합니다. 따라서 8 비트 변수를 원하면 해당 유형을 사용해야합니다.

결과적으로 여전히 메모리에 값을 저장하게됩니다 (더 간단하지만). 나는 C가 좋지는 않지만 IMHO, RAM 대신 레지스터에 변수를 넣고 싶다면 변수를 레지스터에 할당하는 옵션이 있습니다. 다음과 같은 것 :

register unsigned char VARNAME asm("r3");

이러한 트릭에 모든 레지스터를 사용할 수있는 것은 아닙니다.

결론은? 프로그램을 조립하여 작성하십시오. 그것들은 항상 작고 빠르며 읽기 / 지원하기 쉽습니다.


C보다 어셈블리가 읽기 쉽다?
dext0rb

@ dext0rb-그렇습니다. 물론 둘 다 충분히 알고 있다면. C 만 알고 있다면 어셈블리 및 다른 언어를 읽기가 어렵습니다.
johnfound

마지막 요점에 동의하지 않아야합니다. 어셈블러로 작성된 프로그램은 읽기가 훨씬 어렵습니다. 위에 주어진 소스 코드를 비교하십시오. C 코드는 의도가 훨씬 더 명확하고 짧습니다. 이 차이는 구조체가 사용될 때만 커집니다.
soandos

@soandos-C 코드가 더 짧습니다. 더 깨끗해? 확실하지 않습니다. 그렇다면 위의 질문을 전혀 요구할 필요는 없습니다. 실제로 "짧음"의 가격은 세부 사항의 "흐림"입니다.
johnfound

물론, "C가별로 좋지 않다"고 말하는 사람은 순수한 집회의 미덕을 선포 할 것입니다. : D
dext0rb
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.