티스토리 뷰

심화

변수는 여기에!

Just4Fun 2016. 4. 23. 10:40

이전 글에서 예제로 나온 코드들에서 'loop_count'가 프로그램 안에서는 어디에 존재하고 어떻게 접근하는지 하나씩 분석해 보도록 하겠다.  변수를 어디에 정의해서 어떻게 사용하든지 원하는 동작만 하면 되는거 아닌가? 라고 생각할 수도 있겠지만 프로그램의 구조를 알고 있어야 문제가 발생했을 때 훨씬 쉽게 그 문제를 해결할 수 있는 지름길로 갈수 있기 때문이다.

C로 만든 코드를 보드에서 실행 하였을 때, 절대로 눈에 보이는 c코드대로 CPU가 동작되지 않는다.  정확한 동작을 이해하기 위해서는 반드시 어셈블 코드를 분석하여야 한다.  어셈블 코드를 분석할 때 map 파일을 참조하면 많은 도움을 받을 수 있다.

이번 글에서는 각각의 코드를 빌드한 후 생성되는 .lss 파일과 .map 파일을 열어서 'loop_count'변수를 어떻게 처리하는지 분석할 것이다.

첫번째 예제로 나온 코드부터 분석해 보도록 하겠다.

3번 줄에서 loop_count를 함수내 변수로 int 타잎으로 정의하였고, 초기값은 없다.  8번 줄에서 20으로 값을 대입하였다.  map 파일을 열어 loop_count를 찾아본다.  map파일 검색 결과 loop_count는 없는걸로 나온다.  그렇다는 얘기는 loop_count 변수가 메모리상에 존재 하지 않다는 얘기가 된다.  그럼 어셈블코드를 분석하여 실제 CPU는 어떻게 loop_count를 처리하는지 확인해 보자.

어셈블 코드를 보니 loop_count가 r18,r19 레지스터를 이용하는것으로 나타났다.  loop_count가 int 로 선언되었기 때문데 두개의 레지스터가 사용되었다.  r18,19의 초기값으로 20이 들어가는 것이다.  그리고 이들 레지스터에서 1씩 감소하여 그 값이 0이 될때까지 for문을 수행하는 것으로 나온다.

결론은 함수내에서 선언된 변수는 CPU내부 레지스터를 사용하게 된다는 것이다.  그렇다고 해서 늘 항상 그렇게 동작되지는 않고 대부분 그렇게 동작한다. 만약 함수가 복잡하고 길게 작성되었다면 레지스터를 사용하지 않고 스택을 사용할 수도 있다.  어느 방법을 사용하는지는 어셈블 코드를 봐야지만 알 수 있다.


두번째 예제를 분석해 보도록 하겠다.

첫번째 예제와 차이점은 변수 선언과 동시에 초기값을 할당한 것이다.  map 파일에는 역시 loop_count를 찾을수 없으므로 메모리에 할당되지는 않은것 같다.  어셈블 코드를 확인한 결과 첫번째 예제와 달라진것이 없다.  결론은 첫번째예제와 100% 동일하게 동작된다는 것이다.  따라서 변수 선언할 때 초기값을 주는 것이나, 변수 선언 후 값을 할당하는것이나 어셈블 코드는 동일하게 생성된다는 것을 확인하였다.


세번째 예제를 분석해 보도록 하겠다.

첫번째 예제와 동일하게 함수내에서 변수 선언을 하였지만 static 이라는 키워드를 int 앞 부분에 추가하였다.  map파일 검색 결과 다음과 같은 내용이 발견 되었다.

1번줄에 보이는 .bss 는 output section name이다.  나중에 링크 스크립트 부분에서 설명할 예정이므로 지금은 자세히 알 필요는 없다.  단지 1번줄 이후에 나오는 심볼들은 모두 bss 메모리 영역에 할당된 것들이다라는 것만 알고 있으면 된다.

5번 줄에 .bss.loop_count.1612라는것이 보인다.  이것이 setup()함수에서 선언한 loop_count에 대한 심볼에 해당되는 것이다.  map 파일에 나타났으므로 loop_count 변수는 이제 메모리 상에 존재하게 된다는것을 의미한다.  그 위치는 bss 섹션에 있고 심볼명은 loop_count.1612가 된다.  왜 그냥 loop_count가 아니고 loop_count.1612라는것이 되었을까?  그 이유는 static으로 선언되었기 때문이다.  setup() 함수에서 static으로 선언된 loop_count는 setup() 함수 내에서만 사용할 수 있게 된다.  즉, 다른 함수에서도 static int loop_count를 선언해도 전혀 문제 없이 동작된다.  그렇게 되면 이때 선언된 loop_count는 loop_count.xxxx라는 다른 번호의 심볼명을 갖게 되어 setup() 함수에서 선언된 loop_count와 구별할 수 있게 만든다.

setup() 함수에서 선언된 loop_count가 메모리 상에 존재하게 되었으므로 당연히 주소를 할당 받았을 것이다.  map파일에 나온 결과를 보면 0x00800100에 두바이트 크기로 할당된 것을 알 수 있다.  원래 SRAM 메모리는 0번지에서 부터 시작되지만 컴파일할 때 링커는 폰노이만 구조의 메모리 구조를 기반으로 실행파일을 만들게 된다.  따라서 하바드 구조에서는 프로그램 영역과 데이터 영역을 구별해 주어야 한다.  그런 이유로 0x00800000 이 추가 되었다.  어차피 이런 메모리 값은 AVR에서 사용할 수 없는 영역이므로 실제 코드에서는 이 주소를 무시하도록 되어 있다.

어셈블 코드를 보면 어떻게  동작되는지 확실하게 알수 있다.  일단 r24,r25레지스터에 20이라는 숫자를 넣고 난 다음에 이를 0x0100,0x0101 주소에 쓴다.  즉, loop_count가 위치한 메모리에 20이라는 숫자를 기록하는 것이다.  그리고 for 문의 비교항에 해당 코드를 보면 0x0100,0x0101에서 값을 읽어 와서 r18,r19의 값과 비교해서 loop_count 값보다 작으면 for문을 계속 실행하게 된다.

이로써 비록 함수내에서 선언된 변수라도 static으로 선언하게 되면 메모리에 할당 받게 된다는 것을 알수 있게 되었다.


네번째 예제를 분석해 보도록 하겠다.

세번째 예제와 차이점은 변수 선언과 동시에 초기값을 할당한 것이다.  그런데 map파일 검색 결과 loop_count가 검색되지 않는다. 그렇다는 얘기는 메모리에 할당이 안되었다는 뜻인데, 어떻게 된 일인지 어셈블 코드를 확인해 봐야겠다.

놀랍게도 첫번째, 두번째 예제와 동일한 어셈블 코드가 보인다.  컴파일러 입장에서는 loop_count가 단순히 for문을 수행하는데에만 사용되고 다른데에서는 사용되지 않으므로 최적화 하면서 비록 static으로 선언되었지만 메모리 할당을 생략한 것이다.  그만큼 메모리 접근을 하지 않게 되므로 성능이 높아지게 된다.

결론은 비록 static으로 변수를 선언할지라도 항상 메모리에 할당되지는 않고 컴파일러가 상황에 따라 내부 레지스터를 이용할수도 있다는 것을 확인하였다.  따라서 너무 C언어를 책에서 나왔던 내용만 믿고 프로그램을 해서는 안되고 의심나는 부분이 있으면 반드시 map파일과 어셈블 파일을 분석해 볼것을 권고한다.


다섯번째 예제로 넘어가겠다.

loop_count 변수를 함수 밖에서 선언하였다.  이런것을 전역 변수로 선언하였다라고 한다.  map파일을 검색하였더니 다음과 같은 내용이 나온다.

이번에도 bss메모리 영역에 할당된것으로 나온다.  함수 내부에서 선언되었을때와 차이점이라면 6번줄에서 나오는것처럼 COMMON이라는 섹션에 위치한다는 것이다.  그리고 심볼명도 변수명과 같은 loop_count 그대로 나온다.  이 얘기는 loop_count가 전역 변수로 선언되었기 때문에 이제 더이상 그 어디에서도 같은 이름의 전역 변수를 할당할수 없다는 얘기가 된다.  함수 내부에서 로컬변수로 선언하거나 static을 붙여 주어야 loop_count라는 변수를 사용할 수 있게 된다.

어셈블 코드는 세번째 예제와 같은 모양으로 생성된것을 볼 수 있을것이다.


여섯번째 예제를 설명하도록 하겠다.

함수 밖에서 선언과 동시에 초기값을 할당하였다.  map파일 검색 결과 다음과 같이 나온다.

loop_count가 이제는 data 섹션에 할당된것으로 나온다.

setup()함수가 시작되면 r24,r25레지스터값을 loop_count가 있는 메모리에서 값을 읽어와 할당한다.  이 얘기는 이미 loop_count 메모리에 초기값이 들어있다는것을 전제로 하는 것이다. 즉 20이라는 숫자가 0x0100,0x0101에 있다는 뜻이다.  읽어온 값이 0인지 아닌지 검사해서 0이면 즉각 setup()함수에서 빠져 나오고 그렇지 않으면 다음 코드를 수행한다.

for문이 한번 수행되면 다시 loop_count변수 값과 비교하여 그 값보다 적으면 for문을 수행하고 그렇지 않으면 for문을 빠져 나온다.

결론은 전역 변수로 선언된 변수가 초기값을 가지면 data 섹션에 할당이 되고, 초기값을 주지 않고 변수 선언만 할 경우에는 bss섹션에 할당된다는 것을 알수 있다.

이 때 한가지 궁금증이 생겨야 한다.  어차피 data 섹션은 SRAM영역에 있는데 전원이 나가면 SRAM영역은 모두 지워지게 된다.  그런데 어떻게 SRAM영역에 초기값이 들어갈수 있는 거지?  그리고 전역변수 선언할 때 초기값을 주지 않으면 기본적으로 0으로 초기화 된다는데 그건 누가 해주는 거지?  답은 각자 찾아보기 바란다.

또 한가지 다섯번째 예제와 여섯번째 예제에서 전역 변수 선언할 때 static 키워드를 앞부분에 넣어서 map파일을 확인해 보기 바란다.  또한 어셈블 코드도 어떻게 바뀌는지도 확인하여 static이 있고 없고에 따라 어떻게 달라지는지도 확인해 보기 바란다.


마지막으로, 일곱번째 예제를 설명하도록 하겠다.

위의 코드는 AVR 프로그램에서만 사용할 수 있는 코드이다.  이 코드를 사용하기 위하여 <avr/pgmspace.h> 파일을 include 하여야 한다.  이 코드 자체에 대해서는 설명을 생략하기로 하고, map 파일에서 loop_count를 검색해 보기로 한다.

map 파일에서 검색 결과 이번에는 loop_count가 text 섹션 영역의 0x68번지에 할당된 것을 볼 수 있다.  text 영역은 코드만 모아 놓은 곳이고 Program memory 영역에 존재하는데 어떻게 변수데이터가 프로그램 영역에 놓일수 있는거지?

답은 loop_count를 const 키워드를 사용하여 정의하였다. 즉, loop_count 값은 read-only 데이터 변수인것이다.  그리고 PROGRAM이라는 것을 loop_count 선언할 때 추가 하였는데, 이로 인해 data 섹션이 아닌 text 섹션에 변수가 할당될 수 있는 것이다.  좀 더 자세히는 .progmem 섹션에 할당이 되고, progmem이 다시 text 섹션에 포함되도록 한 것이다.  어셈블 코드는 어떻게 되어 있는지 확인해 보자.

r20,r21레지스터에 loop_count가 있는 주소를 써준다음 Z레지스터에 복사한다.  프로그램 메모리 영역에서 데이터를 읽어 오기 위해서는 'lpm' 명령을 사용해야 된다고 입문편 LED 제어 4에서 설명하였다.  r24,r25에 loop_count의 초기 설정값인 20을 읽어서 넣어준다.  for 문이 수행될 때 i값을 비교하기 위하여 계속 Z레지스터에 들어있는 loop_count 위치에서 lpm 명령을 이용하여 읽은 값과 비교하도록 한다.


이번글에서는 변수를 어떻게 선언하느냐에 따라 실행파일이 다양한 모습으로 나타나는 것을 보았다.  어떻게 보면 별 쓸모 없는 얘기인것 같지만 임베디드 프로그램 할 때는 반드시 알아두어야 할 내용인지도 모르겠다.  이러한 내용을 알고 있으면 변수 선언할 때, 이 변수는 어떤 메모리 영역에 놓이게 하겠다라고 미리 설계를 해 볼수 있게 된다.   이러한 내용을 모르고 있으면 쉽게 해결할 수 있는 문제인데도 프로그램이 불가능하다고 판단할 수 있게 되는 것이다.  입문편에서 설명한 예제 중에 CRC 계산을 위해서 CRC 테이블을 미리 만들어 놓으면 성능을 높일수 있다고 했는데, 그러자니 메모리의 수십 %를 점유하여야 한다.  그러나 어차피 CRC 테이블은 read-only 값이므로 위의 일곱번째 예제에서와 같이 progmem 영역에 할당하게 되면 SRAM 메모리 영역이 아닌 프로그램 메모리 영역을 사용하게 되므로 메모리 부족 현상을 해결할 수 있게 되는 것이다.


심화 과정 목차

'심화' 카테고리의 다른 글

random 함수  (4) 2016.06.12
EABI(Embedded Application Binary Interface)  (0) 2016.04.23
엔디안(Endian)  (0) 2016.04.23
변수는 어디에?  (1) 2016.04.20
심화 과정 목차  (0) 2016.02.16
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
글 보관함