[Rookiss C++] 함수와 스택 프레임

황교선·2023년 3월 18일
0

cpp

목록 보기
8/19

함수

일부 작업을 수행하는 코드 블록

  • 호출자가 함수에 인수를 전달할 수 있도록하는 입력 매개 변수를 필요에 따라 정의할 수 있음
    • 입력 매개 변수도 함수의 지역 변수로 함수가 종료되면 메모리에서 반환됨
    • 가변 매개변수라 하여 갯수를 특정짓지 않아도 되는 형태가 있지만 보통 사용하지 않음
  • 필요에 따라 출력으로 값을 반환할 수 있음(반환값)
    • 보통은 반환값이 없거나 하나임
    • 여러 값 반환하는 방법은 반환하고 싶은 자료형들이 있는 struct 타입을 만들어 하나의 반환값으로 사용
  • 일반 작업을 캡슐화하여 재사용 가능한 단일블록으로 만드는데 유용
  • 함수 선언의 필수 요소
    • 함수가 반환하는 값의 형식이나 반환되지 않을 때는 void를 반환타입 적는 곳에 써야함
    • 함수의 이름은 문자 또는 밑줄로 시작해야하며 공백을 포함할 수 없음
    • 매개 변수 목록은 중괄호로 구분되거나 쉼표로 구분된 0개 이상의 매개 변수 집함이어야함
  • 함수를 선언, 정의할 때는 입력 값을 매개변수라 칭하고 호출할 때는 인자라고 부름
  • 코드 상에서 호출할 함수보다 위에서 정의되거나 선언되어있어야함
반환타입 함수이름(매개변수의자료형 매개변수이름) // 매개변수는 없을수도 있고, 여러개 일 수 있음
{
    문장1; // 문장은 여러 개일 수 있음
    return 반환타입의 변수 혹은 상수 // 반환 값은 하나 혹은 없음(void), return은 여러 개일 수 있으며 꼭 마지막 줄에 있지 않아도 됨
}
void Print3HelloWorld()
{
    cout << "Hello World" << endl;
    cout << "Hello World" << endl;
    cout << "Hello World" << endl;
}

void PrintNumber(int num)
{
    cout << "Print : " << num << endl;
    return;
}

int main()
{
    Print3HelloWorld();

    int a = 10;
    PrintNumber(a);
    PrintNumber(20);

    Print3HelloWorld();

    return 0;
}
// 출력 결과
// Hello World
// Hello World
// Hello World
// Print : 10
// Print : 20
// Hello World
// Hello World
// Hello World

Print3HelloWorld 함수는 반환값이 없으며(void) 매개변수도 없다(), return 또한 반환값이 없어서 사용하지 않았지만 맨 마지막 줄에 있다고 생각하면 된다.

Print3HelloWorld라는 함수는 세 번의 “Hello World”를 출력하게 만드는 기능을 한다. 그래서 우리가 일일이 출력하는 문장을 치지 않아도 Print3HelloWorld를 호출하기만 하면, 호출한 부분에서 저 세번의 출력을 하게 된다. 이처럼 일반 작업을 캡슐화(묶어서 코드 블록으로 만듬)를 하여 재사용 가능하게 만들었다. main 함수의 맨 마지막에서 다시 한 번 호출하여 세 번의 출력을 대신하였다.

중간에는 정수를 출력하게 하는 함수인 PrintNumber가 있는데, 정수 자료형 매개변수 하나를 받고 함수 내에서는 그 정수 변수를 num이라는 이름을 사용하여 쓴다. 이 함수 또한 반환값은 없다.

int sum(int a, int b) // 이 sum이란 이름의 함수의 반환값은 int형으로 정수 값을 반환, 두 개의 입력 매개변수를 받으며 각각은 a, b라는 이름으로 이 함수 내에서 사용되는 지역변수
{
    return a + b; // a와 b를 더한 값을 반환, 이 함수를 호출한 곳으로 보냄
}

int main()
{
    int i = sum(10, 32);
    int j = sum(i, 66);
    cout << "The value of j is" << j << endl; 
}
// 출력 결과
// 108

sum이라는 함수는 반환해야하는 값이 정수형이고, sum 함수를 부르기 위해서는 두 개의 인자를 넘겨주어야한다. int i 는 sum을 호출하고 반환되는 값을 사용하는데, sum을 호출할 때 인자로 10과 32를 넣은 후 나온 값이다. sum 함수의 기능은 두 인자를 더한 값을 반환하니 42라는 값이 되겠다. 즉, i는 42라는 값을 초기값으로 갖게 된다. j 또한 마찬가지로 sum을 호출하지만 인자가 42가 들어있는 i와 상수 66을 넣은 후 나오는 반환값 108을 초기값으로 받는다.

스택

갑자기 자료구조 스택을 얘기하는 이유는 함수가 호출될 때 함수가 가지는 공간인 스택 프레임이 만들어지고 이 스택 프레임이 스택을 이용했기 때문이다.

후입선출(Last In First Out) 특성을 가지는 자료구조
(보통 깔끔하게 정돈된) 무더기, (어떤 곳에 물건을 쌓아서) 채우다

  • 먼저 들어오는 데이터가 쌓여서, 최근 데이터가 위에 두어짐
  • 스택에서 데이터를 꺼낼 때는 가장 최근 데이터(맨 위에 쌓여있는)가 먼저 꺼내짐
  • 쌓여있는 책에서 맨 위로 책을 두고, 맨 위의 책을 꺼내는 모습을 상상하면 됨
  • PUSH : 스택에 데이터를 집어넣을 때 사용하는 단어
  • POP : 스택에서 데이터를 꺼낼 때 사용하는 단어

메모리 구조

스택과 더불어 함수의 호출 방식을 이해하기 위해서는 메모리의 구조를 알아야한다. 메모리의 스택 영역에서 스택 프레임이 만들어지고 함수를 호출할 때 이 스택 프레임을 만들기 때문이다. 스택 영역만 설명하면 되긴하지만 그래도 전체적으로 한 번 설명한다.

프로그램을 실행하면 운영체제는 메모리에 프로그램을 위한 공간을 배정하고 프로그램은 그 공간을 사용한다. 프로그램의 구성은 크게 네 가지로 낮은 주소부터 코드 영역, 데이터 영역, 힙 영역, 스택 영역으로 이루어져있다. 힙 영역은 낮은 주소부터 높은 주소로 점점 사용해나가고, 스택 영역은 프로그램이 할당 받은 높은 주소부터 낮은 주소쪽으로 사용한다.

http://www.tcpschool.com/lectures/img_c_memory_structure.png

코드 영역

실행할 프로그램의 코드가 저장되는 영역

  • 텍스트 영역이라고도 함
  • CPU는 코드 영역에 저장된 명령어를 하나씩 가져가서 처리
  • 상수도 이 곳에 포함

데이터 영역

프로그램의 전역 변수와 정적 변수가 저장되는 영역

  • 데이터 영역은 프로그램의 시작과 함께 할당
  • 프로그램이 종료되면 소멸

힙 영역

사용자가 직접 관리할 수 있고 관리해야만하는 영역

  • 사용자에 의해 메모리 공간이 동적으로 할당되고 해제됨
  • 메모리의 낮은 주소에서 높은 주소 방향으로 할당

스택 영역

함수의 호출과 관계되는 지역 변수와 매개 변수가 저장되는 영역

  • 함수의 호출과 함께 할당되며 함수의 호출이 완료되면 소멸
  • PUSH를 통해 데이터를 저장
  • POP을 통해 데이터를 인출
  • LIFO 방식에 따라 동작
  • 함수의 호출 정보를 스택 프레임이라 함

스택 프레임

함수가 호출되었을 때 그 함수가 가지는 공간 구조

http://www.tcpschool.com/lectures/img_c_stackframe_01.png

첫 번째 그림에서 보면 각 함수를 호출할 때마다 아래부터 매개변수(인자), 반환 주소값, 지역 변수 순으로 스택에 쌓고 있다. 이것이 스택 프레임이고 스택 프레임은 각 함수를 호출할 때마다 생성된다. 또한 각 함수의 호출이 끝난 뒤 그 함수의 스택 프레임은 사라진다.

main 함수에서 func10 함수를 호출 할 때 호출한 함수인 main 함수의 스택 프레임은 스택에서 사라지지 않으며, 그 위로 func10 함수의 매개변수, 반환 주소값, 지역 변수가 쌓인다. func10 함수가 끝날 때 이 함수의 스택 프레임이 사라진다. 각 함수의 호출이 끝나면 위 그림의 역순으로 스택 프레임이 하나씩 사라진다.

어셈블리단의 스택 프레임

위에서 설명한 내용은 함수 단위로 스택 프레임을 살펴보았고, 여기에서 스택 프레임 자체에 대해서 좀 더 살펴본다. 함수가 불리는 과정을 가장 로우 레벨 단위로 바라볼 수 있는 어셈블리로 보기위해 어셈블리 용어부터 설명한다.

용어 설명

  • ESP (Extended Stack Pointer)
    • 현재 스택의 가장 위에 들어있는 데이터를 가르키는 포인터
    • ESP는 PUSH 위치가 아닌 POP으로 뽑아낼 데이터의 위치를 가르킴
  • EBP (Extended Base Pointer)
    • 현재 스택의 가장 바닥을 가르키는 포인터
    • 새로운 함수가 호출될 때 EBP 값이 지금까지 사용했던 스택 꼭대기에 위치하고 새로운 스택 시작
    • EBP는 새로운 함수 호출되거나 현재 함수가 종료될때 달라짐
  • CALL (Call a Procedure)
    • procedure는 어셈블리에서 함수를 말함
    • JMP 명령어 같이 프로그램 실행 흐름이 변경되지만 반환 주소를 스택에 저장
    • 반환 주소를 통해 함수 호출 후 원래 위치로 실행 흐름을 되돌릴 수 있음
  • RET, RETN
    • CALL로 호출된 함수를 종료하고 CALL 다음의 명령줄로 이동
  • PUSH
    • ESP의 값이 감소하고, 이 위치에 새로운 값이 채워짐
  • POP
    • ESP가 가르키는 위치의 값을 복사하고, ESP의 값이 증가함
  • MOV
    • 값을 복사하는 명령어
    • ex) MOV A B
      • B의 값을 A에 복사함

함수 호출 시 실행되는 어셈블리어 코드

https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&fname=https://blog.kakaocdn.net/dn/bkmFox/btroT6wlNS1/PYFIFmQULZxBeIykUhPbf1/img.png

위의 한 함수의 스택 프레임을 더 세분화하면 다음 그림과 같이 된다.

  1. 매개변수로 들어오는 인자들이 역순으로 하나씩 스택에 쌓이며,
  2. 반환 주소 값과, EBP 주소 값을 저장하고
  3. 지역 변수를 스택에 쌓는다
; 어셈블리에서 주석은 ;로 시작
; 함수의 매개변수(인자)
PUSH 인자3
PUSH 인자2
PUSH 인자1
; 함수의 반환 주소
CALL 함수주소
PUSH EBP
MOV EBP, ESP
SUB ESP, 100H
; 함수의 지역변수
; PUSH 지역변수
; ...
; 함수의 호출 종료 시
MOV ESP, EBP
POP EBP
RETN
; 함수 호출 종료

참고한 글

profile
성장과 성공, 그 사이 어딘가

0개의 댓글