2023. 1. 7. 17:22ㆍC언어 복습
1. inline 함수
C/C++을 배웠던 사람이라면 다음과 같은 반응을 보일지도 모른다.
'아니, inline 함수는 C++에 있는 기능 아니었어? C에도 있다고?'
그렇다! C에도 inline이 존재한다. 정확히 말하면 원래 C++에 있던 inline 함수를 C99로 가져온 것이다. 그래서 C99부터는 inline 함수를 지원한다. 그리고 Intro에서 C89를 기준으로 글을 작성한다고 나와있을텐데 이번 글부터 다룰 내용은 C99에서 추가된 내용을 다룬다. 어쨋든 inline 함수에 대해서 알아보자.
inline 함수를 배울 때, 보통 매크로 함수에 대해서 먼저 다루는 경우가 많다. 왜냐하면 inline 함수를 잘만 사용하면 매크로 함수의 단점을 해결해주기 때문이다. 우선 매크로 함수의 대표적인 특징을 보면 다음과 같다.
- 매크로 함수 내부에 중단점을 찍을 수 없어서 디버깅이 매우 힘들다.
- 가독성이 너무 떨어진다.
- 함수를 호출할 때에 대한 과부하가 없다.
여기서 장점은 가져오고 단점은 빼버리려고 만든 것이 inline 함수인 것이다. 선언 방법은 아래와 같다.
inline 반환형 함수이름(매개 변수 리스트)
{
함수의 내용
}
보면 알겠지만 그냥 기존의 함수 선언 방식에서 앞에 inline만 붙이면 된다. 이렇게 되면 다른 부분에서 inline 함수를 호출하면 내부적으로 함수를 호출하기 위해 스택 메모리를 할당하는 것이 아니라 그냥 함수의 내용을 매크로 함수처럼 직접 호출하는 것이 아니라 융통성있게 잘 복붙을 해서 최적화를 해줄 것을 컴파일러에게 "요청"하게 된다.
"요청이라는 것은... 컴파일러가 그렇게 하지 않을 수 있다는 뜻인가..?"
그렇다! inline 키워드는 그저 컴파일러에게 최적화가 가능하다는 힌트를 제공해주는 것 뿐이며 컴파일러가 실제 함수 내부를 보고 이를 무시할 수 있다. 또한 요즘 나오는 꽤 좋은 컴파일러들은 inline을 붙이지 않아도 알아서 최적화를 해주는 경우도 많다. 그래서 이게 정말 의미가 있는지 의문을 가질 수 있는데 강의에 따르면 업계에서는 아직 inline을 사용한다고 한다. 왜냐하면 컴파일러가 100% inline을 시켜준다는 보장이 없기 때문에 사람이 직접 inline을 시켰을 때 성능이 향상된다는 것을 알고 있다면 그것을 컴파일러에게 알려주는 것이 더 좋다는 것이다.
2. inline 주의점
보통 우리가 C에서 함수를 선언하고 정의할 때, 선언하는 부분을 .h 파일에 넣고 정의하는 부분은 .c 파일에 넣었다. 하지만 inline 함수를 사용할 때, inline 함수를 호출하는 부분에 컴파일러가 inline 함수의 내용을 융통성있게 잘 넣어주도록 하려면 inline 함수의 내용을 알고 있어야 할 것이다. 그렇기 때문에 inline 함수를 사용할 때는 .h 안에 선언과 정의를 모두 적어야 하는 것이다. 아래의 코드처럼 말이다.
Sample.h
#ifndef __SAMPLE_H__
#define __SAMPLE_H__
void func(void);
inline void in_func(void)
{
/* 아주 멋진 코드 */
}
#endif
Sample.c
#include "Sample.h"
void func(void)
{
/* 아주 멋진 코드 */
}
main.c
#include "Sample.h"
int main(void)
{
/* 아주 멋진 코드 */
return 0;
}
위의 코드에서 함수 중복 정의에 대한 문제가 생길 수 있다고 생각할 수 있지만 inline을 썼기 때문에 그 문제는 생기지 않는다. inline을 사용해서 컴파일러에게 이 함수의 용도가 호출을 위한 용도가 아니며 링커에게 이 함수를 보여줄 필요가 없다고 미리 말했기 때문이다. 그래서 링크 과정에서 링커가 함수의 이름을 통해서 서로 연결해주는 작업조차 필요하지 않기 때문에 inline 함수는 다른 함수와는 다르게 링커가 미리 해당 함수를 읽을 수 있도록 함수 심볼을 만들지 않는다.
근데 아까도 말했지만 inline이라는 키워드를 넣는다고 해서 컴파일러가 전부 inline으로 처리를 해주는 것이 아니다. 만약 처리가 안된다면 inline함수는 inline처리가 되지않고 일반적인 함수처럼 동작하게 된다. 그리고 그 과정에서 링커가 읽을 수 있는 함수 심볼조차 만들어지지 않게 되는 것이다... 즉, 링커는 해당 함수를 인식해서 함수를 호출하는 부분과 링크를 시킬 방법이 없다는 것이다! 이러면 링크 오류가 발생할 수 밖에 없다.
3. 해결 방법
위의 문제를 해결하는 가장 좋은 방법은 extern 키워드를 사용하는 것이다. 사실 강의에 나온 모든 내용을 요약하면 일단 .h 파일에 inline 함수의 내용을 정의를 하고 .c 파일에서 .h 파일에서 정의한 inline 함수의 원형만 선언해준다. 여기서 중요한 것은 .c 파일에서 원형 선언을 할 때에는 맨 앞에 extern을 붙여야 한다는 것이다.
Sample.h
#ifndef __SAMPLE_H__
#define __SAMPLE_H__
void func(void);
inline void in_func(void)
{
/* 아주 멋진 코드 */
}
#endif
Sample.c
#include "Sample.h"
extern inline void in_func(void);
void func(void)
{
/* 아주 멋진 코드 */
}
이것이 강의에서 소개한 최종적인 inline 함수의 사용법이다. 또한 C++에서 inline 함수를 사용할 경우에는 이런 일을 할 필요가 없다. 여담으로 사실 C++에서는 inline 함수를 사용하는 것을 베스트 프랙티스로 말하고 있지만 C에서는 조금 애매하다고 한다. C에서 inline 함수를 사용하는 것이 좀 불편할 뿐더러 C89에서는 inline을 지원하지 않기 때문이다. 또한 강의에서는 extern 키워드를 .h 파일에서 사용했을 때 나타나는 문제점, inline 키워드가 빠졌을 때 나오는 문제점, static을 넣었을 때의 문제점을 제시했는데 요약해서 써보자면 다음과 같다.
- inline을 쓰지 않았을 때 - 당연하지만 중복 정의 오류가 난다. inline 함수가 아닌 진짜 함수로 인식하기 때문에 링커가 어떤 .c 파일에 있는 함수를 링크시켜야 하는지를 모른다.
- .h에서 extern을 사용했을 때 - 컴파일러가 inline 요청을 무시하고 일반 함수로 간주할 경우 문제가 생긴다. 컴파일러가 무시하는 바람에 inline 함수가 아니라 일반적인 함수가 되었고 따라서 이 경우에도 중복 정의가 된다.
- inline 함수를 static으로 만들 경우 - inline으로 처리되지 않으면 똑같은 함수가 여러 번 만들어진다. static 함수는 하나의 트랜슬레이션 유닛마다 만들어지기 때문에 해당 헤더 파일을 인클루드하는 .c 파일마다 함수가 만들어지는 것이다. 이는 명백한 메모리 낭비이다.
'C언어 복습' 카테고리의 다른 글
POCU C언어 정주행 18회차 - 멀티바이트, wchar_t (0) | 2023.01.09 |
---|---|
POCU C언어 정주행 17회차 - restrict, C99 기능 (0) | 2023.01.07 |
POCU C언어 정주행 15회차 - 어서트 재정의, 전처리 명령어와 장단점 (0) | 2023.01.05 |
POCU C언어 정주행 14회차 - malloc과 free, 메모리 함수, 메모리 관리 기법 (0) | 2022.12.31 |
POCU C언어 정주행 13회차 - 예외 처리, 나쁜 오류 처리, 오류 처리 전략 (0) | 2022.12.30 |