어셈블리어 순환구조(while, for)

msung99·2022년 9월 27일
3
post-thumbnail

Do-While 문

c언어 코드를 goto 버전으로 향상시켜봄


.L2 == loop
jne : ZF 가 1이 아니면 jump를 한다. 즉, x가 0이 아니라면 점프를 한다.


do-while 문과 while 문 비교

do-while 문

while 문

  • while 문이 do-while 보다 비효율적이다!

  • while 문 동작과정 : 아래 그림을 보듯이, 맨 처음부터 아무것도 안했는데 test 로 goto 를 하고(jump 를 하고) test 과 통과하면 loop 로 또 goto 를 하고 그제서야 body 를 수행한다.

이러한 while 문르 동작 구조를 "jump - to - middle" 이라고 한다.

  • do-while 문 동작과정 : 반면 do-while 문은 간단하다. loop 가 돌면서 body 를 한번 수행하고, test 를 통과하면 다시 loop 로 goto 를 해서 또 loop 안의 내용을 수행하는 간단한 과정이다!

while 루프 예제

  • 보듯이 while 문을 jump-to-middle 구조를 가지고 있어서 jump 가 많이 발생하고 비효율적이다!

while문 성능향상 - "-O1" 옵션 (do-while 문으로 변환)

  • "-O1" : 컴파일러한테 "-O" 에다 "1" 옵션을 부여함
    • 1 옵션 부여의 의미 : 컴파일러, 너가 할수있는 향상 기능을 가능하다면 해봐라!
      ( O == "O"ptimize(최적화) 의 줄임말 )

전체그림

  • 1 옵션을 붙였을 떄 어떻게 향상이 되는지를 과정을 살펴보자.

1) 아래와 같은 c언어 while문 코드가 있을떄,

2) 컴파일러와 아래와 같이 do-while 문으로 바꿔버린다.

  • Test를 if문으로 한번 과정을 걸치고, Test 결과에 따라서 do-while 문 쪽으로 빠진다.

3) 위의 do-while 문을 컴파일하면 어셈블리의 goto 버전으로 바꾸면 아래와 같다.


예제

  • 코드 실행 초기에 jump 를 여러번 뛰던 기존의 while문 보다 효율적인 코드로 개선되었음을 알 수 있다!

  • 향상된 while문은 맨 처음에 if문을 통해 Test 과정을 맨 앞에서 걸쳐감으로써 jump 을 할 필요가 없게 만들어졌다!


For문

for문 형태

  • for문의 블록은 크게 init, test, update, body 로 총 4개의 block 으로 나눠볼 수 있을 것이다.
for(init; Test; Update)   // == for(초기값; 조건문; 증감문)
  Body

예제


for문의 컴파일 과정 - while 문으로 변환되는 과정

for문의 컴파일 과정
1 ) while 문으로 변환
2 ) do-while 문으로 변환
3 ) goto 문으로 변환

1) 컴파일러가 컴파일시 for 문을 보자마자 while 문으로 변환한다.

위 그림에 대한 예제를 보면 아래와 같다.

2) 위에서 설명했듯이 while 문은 do-while 문에 비해 비효율적이라서,
컴파일러가 기존의 while 문을 do-while 문으로 변환하고 또 컴파일에서 어셈블리의 goto 버전으로 변환한다.

  • 이때 변환시에 위에서 다루었던 어셈블리 goto문 형태대로 변환한다.

변환된 do-while (어셈블리 goto문) 향상 구조

구조

init

if(!Test)
  goto done;
  
loop:
  Body
  update
  
  if(Test)
    goto loop;
    
done:

cf ) 초기의 test 부분은 optimize 하는 옵션에 따라서 지워질 수 있다!
(=> "-O1", "-O2" 등에서 1, 2 와 같이 부여되는 옵션의 번호에 따라서 지워질 수가 있음)


Switch 문

Jump Table

  • switch 문은 jump table 이라는 것이 필요하다.

  • swtich 문의 각 case 별로 각 Code Block (Body 내용) 을 가지고 있다.

  • 아래 그림의 switch문에서 x의 값에 따라서 다른 jump target 으로 jump 를 해서 code block 를 읽어야한다.
    ( jump target 이란, 각 case 에 대한 code block 을 가지고있는 메모리임 )

  • 어디로 jump 를 뛰어야할지를 가지고 있는 것이 jump table 이다.

즉, jump table == address table

  • jump target 메모리들의 주소값을 저장하는(가리키는) 테이블
    (즉 포인터 테이블임)

예제

위와 같은 c언어 코드를 컴파일하면 아래와 같아진다.

cmpq : $6, %rdi  : case가 6까지만 존재했었다. x가 6을 넘는지 아닌지를 비교한다.

ja .L8 : x가 6보다 크면 .L8 (default)이라는 곳으로 점프하라는 뜻
          => switch 문에서 default 임

jmp : *.L4(,%rdi,8) : x가 6보다 작거나 같으면 case 6개중에 하나이므로, 
                      "8x + *.L4" 로 계산된 메모리 주소로 점프한다.
                    
                      => 이때 L4 란 jump table 의 시작주소를 의미

jump table

jump 방법 2가지

1) Direct : 점프 뛰고 바로 주소주는 것
ex) jump .L8

2) inDirect : 점프를 하는데 address 를 계산하고 그 주소로 뛰는 것
ex) jmp *.L4(, %rdi, 8)


jump table 주소

  • L4 가 시작점이라고 바로 위에서 얘기했었고,
    L8 가 왜 case 0 에 매칭되는지, L3 는 왜 x=1 에 매칭되는지는 이해할 필요없다! 그냥 왜 case 5 와 case 6 같은 애들이 같은 jump table 인 L7 에 매칭되었는지만 이해하면 된다.

Code Block 분석

과정1

imulq : %rdx %rax 를 곱하면 그 결과가 rax 레지스터에 리턴됨(저장됨)

과정2

case 2는 break 문이 없으므로 return 하는 값이 없이 바로 case 3로 어가서 case 3로 수행한다.


profile
https://haon.blog

0개의 댓글