Ch.18.6 Inline Functions
C99의 함수 선언들에는 C89표준에는 없는 새로운 추가적인 옵션을 제공했는데,
바로 inline 키워드이다.
이 키워드는 새로운 선언 지정자로서, Storage class와 type qualifier, type specifier와 구분되는 새로운 키워드이다.
이러한 inline을 이해하기 위해서
우리는 C컴파일러가 함수의 호출과 반환을 관리함으로써 생성되는 machine instruction(기계어)
를 시각화할 필요가 있다.
기계어 수준에서 몇몇 명령어들은 호출을 준비하기 위해 실행되어야 하고,
그 호출은 자채로 함수의 첫번째 명령어로 점프 해야 하며,
실행이 시작될때 함수 자체에 의해 추가적인 명령어들이 실행될 수도 있다.
만약 함수가 인자를 포함한다면, C는 call by value이므로 그 인자를 복사해야한다.
함수로부터 반환하는것은 함수가 호출 되는 것과 함수를 호출한 것, 이 두가지와 비슷 양의 노력을 필요로 한다.
누적된 일들은 함수의 호출을 필요로 하고 후에 그것들의 반환은 자주 overhead라고 불린다.
왜냐하면 그 전과 후에 있는 추가적인 일들은 함수가 처음부터 성취하기 위한 목표였기 때문이다.
비록 함수의 호출 overhead는 아주 조금 프로그램을 늦추지만 특정상황이 수만 수천번 중첩되면
훨씬 느려지긴 하지만 (임베디드 시스템에서 일어날 수 있는 일이다.)
혹은 프로그램이 매우 한정된 데드라인을 만나게 될때도 훨신 속도가 느려진다.
C89표준에서는 함수 호출의 overhead를 피하기 위한 유일한 방법은 parmeterized macro(매개변수가 포함된 매크로)
를 사용하는 것이였다.
매개변수가 포함된 매크로들은 몇몇 결함이 있지만 말이다.
C99는 이러한 문제속에서 더 나은 해결책을 제공한다. 바로 inline 함수이다.
inline함수의 특징으로는 컴파일러가 각 함수의 호출이 있을때마다 기계어 수준에서 함수를 대체한다.
inline으로 함수를 선언하는 것은 컴파일러에게 실제로 함수를 inline하게 만들라고 하는것이 아니다.
이것은 단지 컴파일러에게 함수의 호출을 가능한한 빠르게 하도록 요청하는 것이며,
이러한 요청은 아마 함수 의 호출시에 inline 확장을 하도록 할 것이다.
컴파일러는 이러한 요청을 무시해도 된다.
이러한 층면에서 inline은 register, restrict 키워드와 유사한데,
이러한 키워드들도 컴파일러에게 성능을 향상시키도록 요구하지만 무시될 수 있기 때문이다.
사실 C에서 inline 키워드는 약간 복잡한 점이 존재한다.
만약 inline으로 선언한 함수가 존재한다면, 그 함수의 선언은 external한 linkage를 가지지만,
함수의 정의는 internal한 linkage를 가지기 때문에 다른 파일에서 해당 inline함수를 호출한다면
컴파일 에러가 발생할 것이다.
이러한 에러를 피하기 위한 하나의 방법은 함수의 정의 앞에 static 키워드를 추가하는 것이다.
예를 들면 아래처럼 함수를 정의한다:
static inline void foo(void)
{
printf("foo");
}
이제 foo함수는 internal한 linkage를 가지고 있으므로 다른 파일에서 호출자체가 불가능 할것이다.
다른 파일들은 다른 파일만의 foo에 대한 정의를 포함할 수 있으며 정의는 같아도, 달라도 된다.
다른 방법으로는 다른 파일들에게 external한 정의를 제공하는 것이다.
이를 위해서는 inline키워드를 제외하고 한번더 같은 내용의 선언과 정의를 작성하면 된다.
이러한 방식은 올바르지만, 같은 함수를 두가지로 정의하는것은 별로 좋은 생각은 아니다,
왜냐하면, 프로그램이 수정되면 두 함수가 같은 기능을 한다는 보장이 없기 때문이다.
이를 위한 조금 더 나은 접근이 존재한다. inline함수의 정의를 헤더파일에 작성하는것이다.
그 후 하나의 소스파일에서 extern키워드를 붙이고 inline 키워드를 제외한다:
//foo.h
inline void foo(void)
{printf("foo);}
//a.c
#include"foo.h"
extern void foo(void);
위와 같이 정의해 놓으면, inline함수의 호출이 필요한 어느 파일이던 헤더파일을 포함하기만 하면된다.
a.c파일은 extern 키워드가 포함된 foo함수의 원형이 존재하는데, 이것은 a.c파일에서 foo함수를 extren처럼
다루게 만드는 효과가 있다.
C99의 일반적인 규칙은 만약 특정파일에 있는 모든 상위계층의 함수의 선언이 extern이 아니라
inline을 포함한다면 그 파일의 함수의 정의는 inline이 된다.( 위의 예시에서 extern으로 c파일에서
선언했더라도 상위 헤더파일의 선언이 inline이므로 inline으로 처리된다.)
만약 함수가 프로그램의 어느 곳에서 실행되더라도(inline함수의 정의가 포함된 곳)
extern한 함수의 정의가 다른 파일에서 제공되어야 한다.
함수가 호출될때 컴파일러는 기존의 호출을 선택해 실행할 것이다(함수의 extern한 정의를사용해서)
또는 inline확장을 실행 한다(함수의 inline 정의를 이용해서)
컴파일러가 어떤 선택을 할지는 알 수 없기때문에 두가지 정의가 반드시 존재해야 한다.
위에서 사용한 예시가 두가지 정의가 존재하고 같다는 것을 보장한다.
// 슈밤 이게 뭔소릴까....
inline함수의 한계
inline함수는 일반적인 함수들과 살짝 다른 방식으로 사용되기 때문에,
우리는 이 차이점에 대한 규칙과 제한들을 잘 따라야 한다.
static한 storage duration을 가지는 변수들은 extern한 linkage를 가지는 inline함수에서 특정 문제를 가진다.
결과적으로 C99는 이와 같은 상황에서 아래와 같은 제한들을 둔다(inline이 internal할때는 문제가 없다.)
1. 이 함수에서는 수정가능한 static 변수를 정의할 수 없다.
2. 이 함수에서는 internal 한 변수를 참조할 수 없다.
이러한 함수는 static과 const변수를 정의할 수 있지만.
각 inline 함수의 정의에서는 변수의 복사본을 만들 수 있다.
GCC에서의 inline함수들
GCC를 포함한 몇몇 컴파일러들에서는 C99표준에 앞서 inline함수르 지원했다.
결과적으로 그들의 inline함수를 위한 규칙들은 표준과 약간 다를 수있다.
자세히 예시를 들면 앞서서 예시로 든 foo함수와 a헤더에 대한 예시가 특정 컴파일러들에서는
실해되지 않을 수 있다.
GCC의 버전과 상관없이 static한 inline함수는 항상 사용이 가능하므로 이게 제일 안전한 방식이다.
static inline 함수는 필요에 따라 소스파일이나 헤더파일에 사용이 가능하다.
여러 파일속에서 동일한 inline함수르 사용하는 다른 방법이 존재한다.
하지만 이는 C99표준에는 맞지 않는다.
방법은 바로 헤더파일 내에서 extern inline으로 함수를 정의하고
이 inline함수가 필요한 소스파일에 헤더파일을 포함하는 것이다.
그후 같은 정의를 extern과 inline키워드를 빼고 소스파일에 작성한다.
( 이를 통해 만약 컴파일러가 어떤 이유로든 간에 inline이 불가능 하면, 이 정의를 통해 컴파일한다.)
마지막으로 GCC에 관한 것은 -o옵션을 통해 최적화 되도록 할떄만 함수가 inlined 된다.
**특이 사항: C 에서는 default로 inlnine 함수가 static한 storage class 를 가지므로, linkage가 internal 하지만
C++에서는 default로 inline 함수가 external 한 linkage를 가지므로 VS에서 msvc를 통해 컴파일 할때와
VSC에서 gcc를 통해 컴파일을 할때 결과가 다르게 나온다!
예를 틀면 VS에서 gcc로 컴파일 할때는 linkage관련 컴파일 에러가 등장하더라도
VS에서 msvc를 통해 컴파일할때는 아무런 이상없이 파일이 실행될 수 있다.
두 컴파일에서 동일 한 결과를 얻고 싶으면 inline함수 앞에 extern 키워드를 붙여 linkage를 external하게 만들면 된다.
또한 msvc에서 inline함수가 extern하기 때문에 서로다른 두 파일에서 동일한 이름의 inline함수를 재정의하는 것은 정의되지 않은 행동에 해당한다.