I'm FanJae.

[C++] Function I 본문

C++/Basic

[C++] Function I

FanJae 2024. 8. 7. 12:08

※ 본 포스트는 코드누리 C++ Basic 강의 내용을 보고 정리한 포스트입니다.

 

- Function 이라는 것은 원래 C 언어에도 존재하던 개념이다.

- C 언어에는 없는 C++ 함수만의 특징들이 존재한다.

 

1. Default parameter

#include <iostream>
void foo(int a, int b = 0, int c = 0)
{
     std::cout << a << ", " << b << ", " << c << std::endl;
}
int main()
{
    foo(1,2,3); 
    foo(1,2); // foo(1, 2, 0)
    foo(1);   // foo(1, 0, 0)
}

- C 언어에서는 함수 인자에 값을 넣는 표기 사용이 불가능하다.

- 반면, C++에서는 함수 인자에 Default 값을 넣는 것이 가능하다.

 

Default parameter라는 것은 함수 호출시 인자를 전달하지 않으면, 미리 지정된 인자 값을 사용하게 한다는 것이다.

 

1-1. Default parameter 사용시 주의 사항

① 함수의 마지막 인자부터 차례대로 디폴트 값을 지정해야 한다.

void f1(int a = 0, int b = 0, int c = 0) {} // ok
void f2(int a, int b = 0, int c = 0) {}     // ok
// void f3(int a = 0, int b, int c = 0) {}     // error
// void f4(int a = 0, int b = 0, int c) {}     // error

 

② 함수를 선언과 구현으로 분리 할 때는 함수 선언부에만 디폴트 값을 표기해야 한다.

void foo(int a, int b = 0, int c = 0);

int main()
{
    foo(1);
}

/* Error : 선언에 적어준 내용을 구현에서 바꾸려한다고 착각함.
void foo(int a, int b = 0, int c = 0)
{

} */

// void foo(int a, int b, int c)
void foo(int a, int b /* = 0 */, int c) // Default 값을 표기할때 이렇게 표현하기도 한다.
{

}

 

- 선언에서 Default 값을 적은 것을 구현에서 바꾸려고 하면 안된다. (Compile Error)

- 만약, Default 값을 구현부에 표기하고 싶다면, 주석을 이용해서 표기가 가능하다.

 

1-2. Default parameter의 원리

- 컴파일러가 컴파일 시에 함수 호출하는 코드의 함수 인자에 디폴트 값을 채워주는 것이다.

void foo(int a, int b = 0, int c = 0);

int main()
{
    foo(1, 0, 0);
    foo(1);        // foo(1,0,0)
}

void foo(int a, int b, int c)
{
}

어셈블리로 볼 때, 이 둘은 완전히 동일하다.

- 위는 Compile Explorer(https://godbolt.org/) 를 통해 확인한 것이다. 이 둘이 어셈블리 레벨로 볼때도 동일하다는 것을 확인할 수 있다.

 

※ 이후 가상함수와 디폴트 파라미터에서 조금 더 다뤄질 예정이다.


2. Function Overloading

#include <iostream>

int square(int a)
{
    return a * a;
}

double square(double a) // C에서는 이와 같은 문법을 허용하지 않는다.
{
    return a * a;
}

int main()
{
    auto ret1 = square(3);
    auto ret2 = square(3.4);
    
    std::cout << ret1 << std::endl;
    std::cout << ret2 << std::endl;
}

- 인자의 개수나 인자의 타입이 다르면, 동일한 이름의 함수를 여러 개 만들 수 있다.

- 단, 함수 호출 시 어느 함수를 호출할지 구별할 수 있어야 한다.

 

2-1. 특징

- 함수 사용자 입장에서는 동일한 함수처럼 생각하게 된다. 즉, 사용하기 쉬운 일관성 있는 형태의 라이브러리 구축이 가능하다. 이 때문에, C언어를 제외한 대부분의 최신 언어들이 지원한다.

 

인자의 개수가 달라도, Default Parameter가 있는 경우는 주의 해야한다.

void f4(int a)
void f4(int a, int b = 0) { }

int main()
{
    f4(1, 2); // 이것만 있을때는 문제가 안됨.
    f4(1);    // 이것을 입력하면, Compile Error. Ambiguous 문제 발생.
}

- 어떤 것을 호출해야 할지 애매하다. 즉, 컴파일러가 함수 인자로 보고 어느 함수가 호출될지 결정이 안된다.

 

② 함수 반환 타입만 다른 경우는 오버로딩이 안된다.

void f2(int a) {}
char f2(int a) { return 0; }

- 함수 f2가 받는 Parameter는 동일하다. 이것만 보고, 실행 함수를 판단하는 것은 불가능하다.

 

③ 함수를 호출할 때 모호한 경우

void f5(char a) {}
void f5(short a) {}

int main()
{
    f5('a') // ok
    f5(1); // 1은 short,char 둘다 변환이 가능하여 ambiguous 문제 발생.
}

 

※ 정리하면, Overloading 자체가 안되는 경우가 있고, Overloading은 가능하나 호출시 문제가 되는 경우도 존재한다.

 

2-2. Function Overloading의 원리 (Name mangling)

- 컴파일러가 컴파일 시간에 함수의 이름을 변경하는 것이다.

- 컴파일 된 후의 코드에 있는 함수는 모두 이름이 다르다.

- 이러한 현상을, Name magling이라고 한다.

 

어셈블리로 보면, 두 함수의 이름을 다르게 저장하는 것을 확인할 수 있다.

 

/tc 옵션을 주어 C로 컴파일을 해보면, sqaure라는 컴파일 할때, 이름을 바꾸지 않았음을 확인할 수 있다.

※ mangling 규칙은 컴파일러마다 다르다.

※ 위와 같은 'Name mangling 현상' 때문에 C와 C++ 언어 사이에는 호환성 문제가 발생한다.


3. extern "C"

 

 

 

 

 

 

 

 

 

// square.h
int square(int);

// square.c
int square(int a)
{
    return a*a;
}

// using_square.cpp
#include "square.h"

int main()
{
    int n = square(3);
}

- 이 예제는 확장자에 유의해야한다. 이를 빌드하면, 정상적으로 실행되지 않고 오류가 발생한다.

- square(int)를 찾지 못하는 오류가 나온다.

 

3-1. C/C++ 호환성 문제

- 파일별로 각각 컴파일 후, 생성된 .o(.obj) 파일을 링커가 결합한다.

- cl, gcc 등의 컴파일러는 확장자에 따라 언어를 결정한다.

 

square.c는 확장자가 C이므로, C 문법으로 컴파일을 한다.  즉, square.c는 Name mangling이 일어나지 않는다.

이에 반면, using_square.cpp는 C++ 문법이므로, square 함수에 대한 Name mangling이 일어난다. 이로 인해서 링킹시 mangling 된 square를 찾지 못하는 것이다.

 

// square.h
extern "C"
int square(int);

// square.c
int square(int a)
{
    return a*a;
}

// using_square.cpp
#include "square.h"

int main()
{
    int n = square(3);
}

- 위와 같이 extern "C"라고 적어주면, 이 함수에 대한 Mangling이 발생하지 않는다.

 

3-2. extern "C" 만 사용하는 것의 문제점

- C++ 언어의 문법이다

- C 컴파일러는 extern "C"를 알지 못한다.

 

- 즉, 하나의 헤더 파일을 C/C++에서 모두 사용하려면 조건부 컴파일을 해야한다.

// square.h
#pragma once

#ifdef __cplusplus
extern "C" {
#endif
     int square(int) ;

#ifdef __cplusplus
}
#endif

 

※ 정리하면, 함수 오버로딩(Overloading)은 복잡한 개념이 아니지만, 그 원리인 Name Mangling 때문에 C / C++간 호환성 문제가 발생할 수 있다. 이에 따른 해결법을 반드시 알아야 한다.

'C++ > Basic' 카테고리의 다른 글

[C++] Function III  (0) 2024.08.08
[C++] Function II  (0) 2024.08.08
[C++] C++ 에서 새롭게 추가된 Type & Variable  (0) 2024.08.06
[C++] 표준 입출력 정리  (0) 2024.08.06
[C++] namespace 정리  (0) 2024.08.05
Comments