일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 더 지니어스 양면포커
- suffix return type
- delete function
- virtual function
- C++
- std::ostream
- member function pointer
- constructor
- placement new
- virtual function table
- new&delete
- conversion constructor
- pointer to member data
- std::vector
- std::endl
- diamond inheritance
- virtual destructor
- dynamic_cast
- this call
- discord bot
- vector size
- c++ basic practice
- increment operator
- base from member
- return by reference
- c++ multi chatting room
- vector capacity
- std::cout
- virtual inheritance
- operator overloading
- Today
- Total
I'm FanJae.
[C++] Function I 본문
※ 본 포스트는 코드누리 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 |