인라인 함수
헤더와 두 C++ 소스의 번역 단위·목적 파일·링크를 먼저 살펴보고 inline의 정의 규칙과 실제 인라인화 판단을 구분합니다.
여러 .cpp 파일에서 같은 함수를 쓰려면 선언과 정의를 어디에 둘지 먼저 정해야 합니다. 앞 절의 함수 선언과 정의를 세 파일로 나누어 살펴보겠습니다.
inline은 함수 정의를 공유할 때 지켜야 할 언어 규칙에 영향을 줍니다. 최적화기(optimizer)가 호출을 함수 본문으로 펼치는 실제 인라인화는 별도의 판단입니다. 키워드가 있어도 호출이 남을 수 있고, 없어도 호출이 펼쳐질 수 있습니다.
함수 호출에는 호출·복귀와 호출 규약에 따른 비용이 생길 수 있지만, 소스의 호출이 항상 별도 스택 프레임이나 기계어 call 명령으로 이어지는 것은 아닙니다. 먼저 파일 배치를 이해한 뒤 성능 판단을 살펴보겠습니다.
인라인 함수란 무엇인가?
함수 선언에 inline 지정자를 붙이면 inline 함수를 선언합니다.
inline 반환타입 함수이름(매개변수타입1 매개변수이름1, ...) {
// 함수 몸체
}다음 예제의 getMax는 inline 함수이지만, 이 키워드만 보고 실제 기계어가 호출 치환 형태라고 단정할 수는 없습니다.
#include <iostream>
inline int getMax(int a, int b) {
return (a > b) ? a : b;
}
int main() {
int x = 10;
int y = 20;
std::cout << getMax(x, y) << '\n';
std::cout << getMax(50, 30) << '\n';
}이 예제의 예상 출력은 20과 50입니다. 이 값은 함수가 반환하는 결과이며 실제 호출 치환 여부를 알려 주지는 않습니다.
헤더와 .cpp 경계를 정확히 나누기
보통 각 .cpp는 포함한 헤더 내용과 함께 하나의 번역 단위(translation unit)가 됩니다. 아래에서는 main.cpp와 my_math.cpp를 각각 처리하므로 번역 단위가 두 개입니다.
일반 함수의 세 파일
일반 외부 함수는 헤더에 선언하고 한 .cpp에 정의할 수 있습니다. main.cpp는 add를 직접 호출하고, my_math.cpp의 addFromOtherFile도 같은 add를 호출하도록 만듭니다.
#pragma once
int add(int a, int b);
int addFromOtherFile();#include "my_math.h"
// add의 정의는 프로그램의 이 한 곳에 둡니다.
int add(int a, int b) {
return a + b;
}
int addFromOtherFile() {
return add(30, 40);
}#include <iostream>
#include "my_math.h"
int main() {
int sum = add(10, 20);
std::cout << "합계: " << sum << '\n';
std::cout << "다른 파일의 합계: " << addFromOtherFile() << '\n';
}세 파일을 같은 폴더에 저장합니다. 앞 장의 GCC 환경에서는 두 .cpp를 각각 목적 파일(object file)로 만들고 함께 링크하는 과정을 다음 명령으로 나눠 볼 수 있습니다. 헤더는 각 소스에 포함되며, 별도 목적 파일로 만들어 링크하는 입력이 아닙니다.
g++ -std=c++20 -Wall -Wextra -c main.cpp -o main.o
g++ -std=c++20 -Wall -Wextra -c my_math.cpp -o my_math.o
g++ main.o my_math.o -o inline_demo.exe
.\inline_demo.exe-c는 목적 파일을 만든 뒤 링크 전에 멈춥니다. 마지막 g++ 명령이 두 목적 파일과 필요한 라이브러리를 연결해 실행 파일을 만듭니다. 예상 출력은 다음과 같습니다.
합계: 30
다른 파일의 합계: 70add의 정의를 둔 목적 파일이 링크 입력에서 빠졌는지 먼저 확인하면 정의를 찾지 못하는 링크 오류를 좁힐 수 있습니다. 에디터의 현재 파일만 빌드하는 설정이라면 두 소스를 명시해야 한다는 개발 환경 설정의 안내도 확인합니다.
같은 파일을 inline 정의로 바꾸기
이번에는 add의 정의를 헤더에 둡니다. main.cpp와 my_math.cpp가 모두 이 헤더를 포함하면 두 번역 단위에서 같은 inline 정의를 사용할 수 있습니다.
#pragma once
inline int add(int a, int b) {
return a + b;
}
int addFromOtherFile();헤더로 옮긴 add의 정의를 my_math.cpp에도 남기면 안 됩니다. 앞의 my_math.cpp 내용은 다음으로 대체하고, main.cpp는 그대로 사용합니다.
#include "my_math.h"
int addFromOtherFile() {
return add(30, 40);
}두 소스를 같은 명령으로 다시 컴파일하고 새 목적 파일을 링크합니다. 예상 출력은 여전히 30과 70입니다. addFromOtherFile은 선언만 헤더에 두고 한 .cpp에 정의한 일반 함수로 남습니다.
#pragma once는 이 툴체인에서 한 번역 단위 안의 반복 include를 막는 장치입니다. 서로 다른 .cpp에서 같은 헤더를 포함하는 것까지 막거나 프로그램 전체의 중복 정의 규칙을 해결하지는 않습니다.
헤더에 포함된 inline 정의는 여러 번역 단위에 있어도 ODR(One Definition Rule, 단일 정의 규칙)의 일치 요건을 만족하면 허용됩니다. 이 예제처럼 같은 헤더를 공유하되 정의의 토큰과 그 안에서 찾는 이름의 의미도 일치해야 합니다. 프로그램에서는 주소가 하나인 단일 함수로 취급하며, 특정 링커가 중복 기계 코드를 합치는 동작에만 기대는 규칙이 아닙니다.
C++ inline 헤더와 두 번역 단위의 링크 흐름
동일 헤더를 두 번역 단위가 포함하고 각각 목적 파일을 만든 뒤 함께 링크한다. 단계마다 main.o/my_math.o 생성 여부가 달라지며 출력 30/70을 인라인 치환 증거로 해석하지 않는다.
HEADER · TRANSLATION UNIT · LINK
두 번역 단위가 같은 inline 정의를 공유한다
헤더에 옮긴 add 정의를 두 .cpp가 함께 사용합니다. 결과가 30과 70이라는 사실만으로 호출 치환이나 성능 이득을 판단할 수는 없습니다.
헤더 포함 → 각 소스 컴파일 → 함께 링크
my_math.h에 정의inline int add(int a, int b)의 본문과 일반 함수addFromOtherFile()의 선언을 둡니다. 옛my_math.cpp의add정의는 제거합니다.헤더를 포함한 번역 단위 두 개
main.cpp는add(10, 20)을 호출하고,my_math.cpp의 래퍼는add(30, 40)을 호출합니다. 두 소스 모두 같은 헤더 정의를 포함합니다.main.o와my_math.oGCC의
-c로 각 소스의 목적 파일을 만듭니다. 헤더는 별도 링크 입력이 아닙니다. 구체적인 함수 코드·심볼 배치는 최적화에 따라 달라질 수 있습니다.하나의
inline_demo.exe로 링크두 목적 파일을 함께 연결합니다. 예상 출력은 합계 30과 다른 파일의 합계 70입니다. 이 일반 헤더 모델에서 inline 정의는 ODR 요건을 만족할 때 하나의 함수로 취급됩니다.
파일 배치가 실제 호출 치환을 강제하지 않는다
분석 가능한 본문 확보
같은 번역 단위에 본문이 있으면 바로 분석할 수 있고, LTO를 사용하면 여러 번역 단위의 정보를 결합할 수도 있습니다.
이익과 비용 비교
본문 크기, 호출 빈도, 상수 인자, 최적화 옵션과 profile 정보, 코드 증가와 instruction-cache 비용을 함께 봅니다.
펼치거나 호출을 유지
add(10, 20)을 펼치거나 상수 계산으로 줄일 수도 있고, 호출을 남길 수도 있습니다. 반환값 30만으로 어느 변환이 있었는지 알 수 없습니다.치환하지 않아도 정의 규칙 유지
inline없는 함수도 펼칠 수 있고 inline 함수의 호출도 남길 수 있습니다. 정의 도달성과 ODR 규칙은 실제 치환 여부와 별개입니다.
범위: 일반 헤더와 두 소스 파일의 예제입니다. #pragma once는 한 번역 단위의 반복 include만 막으며 프로그램 전체의 ODR을 대신하지 않습니다. C++20 named module의 정의 규칙은 본문의 선택 심화에서 구분합니다.
이 예의 조건
- 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
- 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.
관계도의 보충 설명
| 범위 | 원문 |
|---|---|
| 관계·조건 설명 | 6개 노드와 6개 간선이다. 동일 my_math.h가 main.cpp와 my_math.cpp 두 번역 단위에 각각 포함된다. 각 소스가 main.o, my_math.o를 만들고 두 목적 파일 모두 최종 실행 파일로 링크된다. 헤더 자체를 목적 파일이나 별도 링크 입력으로 표시하지 않는다. 예상 출력 30/70은 계산 결과이며 실제 기계 호출의 치환 증거가 아니다. 모바일에서도 동일한 fan-out 두 경로와 fan-in이 유지된다. |
| 노드·연결선 라벨 | 30과 70을 출력 |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 공유 선언 두 번역 단위가 같은 헤더를 포함한다. |
|
|
| 별도 컴파일 각 번역 단위가 목적 파일을 만든다. |
|
|
| 링크·실행 목적 파일을 연결한 실행 파일을 실행한다. |
|
|
C++ inline 헤더와 두 번역 단위의 링크 흐름
동일 헤더를 두 번역 단위가 포함하고 각각 목적 파일을 만든 뒤 함께 링크한다. 단계마다 main.o/my_math.o 생성 여부가 달라지며 출력 30/70을 인라인 치환 증거로 해석하지 않는다.
HEADER · TRANSLATION UNIT · LINK
두 번역 단위가 같은 inline 정의를 공유한다
헤더에 옮긴 add 정의를 두 .cpp가 함께 사용합니다. 결과가 30과 70이라는 사실만으로 호출 치환이나 성능 이득을 판단할 수는 없습니다.
헤더 포함 → 각 소스 컴파일 → 함께 링크
my_math.h에 정의inline int add(int a, int b)의 본문과 일반 함수addFromOtherFile()의 선언을 둡니다. 옛my_math.cpp의add정의는 제거합니다.헤더를 포함한 번역 단위 두 개
main.cpp는add(10, 20)을 호출하고,my_math.cpp의 래퍼는add(30, 40)을 호출합니다. 두 소스 모두 같은 헤더 정의를 포함합니다.main.o와my_math.oGCC의
-c로 각 소스의 목적 파일을 만듭니다. 헤더는 별도 링크 입력이 아닙니다. 구체적인 함수 코드·심볼 배치는 최적화에 따라 달라질 수 있습니다.하나의
inline_demo.exe로 링크두 목적 파일을 함께 연결합니다. 예상 출력은 합계 30과 다른 파일의 합계 70입니다. 이 일반 헤더 모델에서 inline 정의는 ODR 요건을 만족할 때 하나의 함수로 취급됩니다.
파일 배치가 실제 호출 치환을 강제하지 않는다
분석 가능한 본문 확보
같은 번역 단위에 본문이 있으면 바로 분석할 수 있고, LTO를 사용하면 여러 번역 단위의 정보를 결합할 수도 있습니다.
이익과 비용 비교
본문 크기, 호출 빈도, 상수 인자, 최적화 옵션과 profile 정보, 코드 증가와 instruction-cache 비용을 함께 봅니다.
펼치거나 호출을 유지
add(10, 20)을 펼치거나 상수 계산으로 줄일 수도 있고, 호출을 남길 수도 있습니다. 반환값 30만으로 어느 변환이 있었는지 알 수 없습니다.치환하지 않아도 정의 규칙 유지
inline없는 함수도 펼칠 수 있고 inline 함수의 호출도 남길 수 있습니다. 정의 도달성과 ODR 규칙은 실제 치환 여부와 별개입니다.
범위: 일반 헤더와 두 소스 파일의 예제입니다. #pragma once는 한 번역 단위의 반복 include만 막으며 프로그램 전체의 ODR을 대신하지 않습니다. C++20 named module의 정의 규칙은 본문의 선택 심화에서 구분합니다.
이 예의 조건
- 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
- 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.
관계도의 보충 설명
| 범위 | 원문 |
|---|---|
| 관계·조건 설명 | 6개 노드와 6개 간선이다. 동일 my_math.h가 main.cpp와 my_math.cpp 두 번역 단위에 각각 포함된다. 각 소스가 main.o, my_math.o를 만들고 두 목적 파일 모두 최종 실행 파일로 링크된다. 헤더 자체를 목적 파일이나 별도 링크 입력으로 표시하지 않는다. 예상 출력 30/70은 계산 결과이며 실제 기계 호출의 치환 증거가 아니다. 모바일에서도 동일한 fan-out 두 경로와 fan-in이 유지된다. |
| 노드·연결선 라벨 | 30과 70을 출력 |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 공유 선언 두 번역 단위가 같은 헤더를 포함한다. |
|
|
| 별도 컴파일 각 번역 단위가 목적 파일을 만든다. |
|
|
| 링크·실행 목적 파일을 연결한 실행 파일을 실행한다. |
|
|
출력이 같다는 사실은 호출이 실제로 펼쳐졌다는 증거가 아닙니다. 목적 파일의 구체적인 함수 코드나 심볼 배치도 최적화에 따라 달라질 수 있습니다. inline은 정의 배치의 규칙을 바꾸며, 호출 치환과 성능은 뒤에서 따로 판단합니다.
세 파일을 직접 변경하고 다시 만들기
inline 헤더의 add 본문만 return a+b+1;로 바꾸세요. 다른 소스는 그대로 두고 두 목적 파일을 모두 새로 컴파일한 뒤 다시 링크하면 두 출력 값은 무엇인가요? 헤더를 포함하는 소스 한쪽만 재컴파일하면 왜 이 연습의 올바른 비교가 되지 않나요?
정의 공유와 측정 범위 확인
직접 호출의 결과는 10+20+1=31, 다른 파일의 호출 결과는 30+40+1=71입니다. 한쪽 옛 목적 파일만 남기면 번역 단위 사이에 서로 다른 inline 정의를 섞어 ODR의 일치 요건을 어길 수 있습니다. 실행 결과가 우연히 같거나 링크가 성공해도 올바른 비교로 볼 수 없습니다. 출력 31·71은 바뀐 함수의 계산을 확인하며, optimizer의 호출 치환 여부나 속도는 별도로 검사해야 합니다.
정의가 겹치거나 보이지 않을 때
inline 헤더로 바꾼 뒤 옛 my_math.cpp의 add 정의까지 남기면, 그 번역 단위에는 헤더의 정의와 소스의 정의가 함께 들어갑니다. 같은 번역 단위의 중복 정의는 진단 대상이며 보통 컴파일 단계에서 재정의 오류가 나타납니다. 표준이 요구하는 것은 진단이고, 오류 문구나 도구가 반드시 빌드를 중단하는 방식까지 고정하지는 않습니다.
반대로 헤더에 본문을 둔 채 inline만 없애면 두 번역 단위에 일반 외부 함수 add의 정의가 생깁니다. 보통 링크 단계에서 다중 정의 오류가 나지만, 이 교차 번역 단위 ODR 위반은 표준상 진단이 보장되지 않습니다. 우연히 링크됐다고 올바른 정의 배치로 판단하지 않습니다.
다른 번역 단위에서 inline으로 선언하고 사용하는 함수의 정의를 한 .cpp에만 숨기는 것도 정상적인 일반 호출 전환이 아닙니다. 호출하는 쪽의 정의 도달성 규칙을 위반하며 진단이 보장되지 않을 수 있습니다. 일반 함수로 분리할지, inline 정의를 헤더로 공유할지 한 방식으로 맞춥니다.
선택 심화: 정의 도달성과 C++20 모듈 규칙
앞의 두 번역 단위 예제는 일반적인 헤더와 소스 파일 모델입니다. 호출하는 번역 단위에도 inline 정의가 필요하다는 요구를 표준 문서는 정의 도달성(reachability) 규칙으로 표현합니다. odr-use에는 이 예제처럼 함수를 실제로 호출하는 사용이 포함됩니다.
정의 영역(definition domain)은 private module fragment 또는 그것을 제외한 번역 단위 부분입니다. 앞 예제에는 모듈이 없으므로 각 번역 단위 전체를 생각하면 됩니다.
inline은 함수의 연결(linkage)을 바꾸지 않습니다. 연결은 서로 다른 번역 단위의 선언이 같은 함수를 가리키는지에 관여하는 규칙입니다.- inline 함수가 odr-use된 각 definition domain의 끝에서는 그 정의가 도달 가능해야 합니다. 호출보다 앞에 항상 정의가 있어야 한다는 뜻은 아닙니다.
- 외부 연결 또는 모듈 연결을 가진 함수가 한 definition domain에서 inline으로 선언되면, 그 함수가 선언된 다른 영역의 끝에서도 inline 선언이 도달 가능해야 합니다. 이 일관성 위반에는 진단이 보장되지 않습니다.
- named module에 붙지 않은 inline 함수는 ODR의 일치 요건을 만족하는 정의를 여러 번역 단위에 둘 수 있습니다. 이때도 프로그램에서는 주소가 하나인 단일 entity입니다.
constexpr함수는 암시적으로 inline이며, global module에서 클래스 정의 안에 정의한 멤버 함수도 암시적으로 inline입니다.
C++20의 이름 있는 모듈(named module)에 붙은 inline 함수는 선언된 definition domain 안에 정의되어야 합니다. 일반 헤더의 같은 정의를 여러 번역 단위에 두는 설명을 모듈에 그대로 적용할 수 없습니다. 클래스 안의 정의가 암시적으로 inline이라는 설명에도 앞의 global module 조건을 유지합니다.
함수 template도 보통 인스턴스화 지점에서 정의를 볼 수 있도록 헤더에 둡니다. 하지만 template의 다중 정의 허용은 template·ODR 규칙에서 오며, 그 이유만으로 inline을 붙일 필요는 없습니다.
template <typename T>
T sum(T a, T b) {
return a + b;
}| 질문 | inline 언어 규칙 | 실제 인라인화 |
|---|---|---|
| 무엇을 정하는가? | 선언·정의 reachability와 ODR | 호출을 본문으로 펼칠지 여부 |
| 헤더에 정의하는 이유 | odr-use하는 번역 단위에서 정의를 보이게 하고 허용된 다중 정의를 사용하기 위해 | 같은 번역 단위에서 본문 분석 기회를 주지만 치환을 보장하지 않음 |
inline이 없으면? | 일반 비-template 외부 함수는 보통 프로그램 전체에 하나의 정의를 둠 | optimizer가 이익이 있으면 여전히 펼칠 수 있음 |
inline이 있으면? | 치환 여부와 무관하게 inline의 ODR 규칙을 지켜야 함 | optimizer가 이익이 없으면 일반 호출을 유지할 수 있음 |
| 코드 크기는? | 키워드 자체로 결정되지 않음 | 실제로 여러 호출 지점에 펼쳤을 때 증가할 수 있음 |
optimizer는 호출 지점마다 다시 판단한다
실제 인라인화는 보통 다음 정보를 함께 사용합니다.
- 함수 본문의 크기와 제어 흐름.
- 호출 지점이 실제로 자주 실행되는지에 대한 정적 추정이나 profile 정보.
- caller에서 보이는 상수 인자와 치환 뒤 가능한 상수 전파·분기 제거.
- target, 최적화 수준, PGO(Profile-Guided Optimization)와 LTO(Link-Time Optimization) 설정.
- 본문 복제로 늘어나는 binary 크기와 instruction-cache 비용.
작고 자주 실행되는 함수는 좋은 후보일 수 있지만, 함수 크기나 호출 형태 하나만으로 결과가 정해지지는 않습니다.
- 재귀 호출을 무한히 펼칠 수는 없어도, 비재귀 호출 지점이나 제한된 깊이, 일부 경로를 인라인화할 수 있습니다.
- 함수 포인터나 virtual 호출도 target을 알아내거나 devirtualize할 수 있다면 직접 호출로 바뀐 뒤 후보가 될 수 있습니다.
- 큰
for루프,switch또는 복잡한 제어 흐름은 비용을 높일 수 있지만 절대적인 비인라인 조건은 아닙니다. - 정상적으로 한
.cpp에 정의한 일반 함수도 LTO가 번역 단위 정보를 결합하면 cross-TU 인라인화가 가능할 수 있습니다. 이는 inline 함수의 정의 reachability 규칙을 대신하지 않습니다.
따라서 getMax처럼 짧은 함수도 “인라인화될 가능성이 있는 후보”라고 표현해야 하며, 최종 결과는 해당 compiler·옵션·호출 지점의 최적화 기록이나 생성 코드를 확인해야 합니다.
인라인 함수와 함수형 매크로
C와 오래된 C++ 코드에서는 호출 모양을 만들기 위해 함수형 매크로를 사용하기도 합니다.
#define SQUARE(x) ((x) * (x))매크로는 함수가 아니라 preprocessing 단계의 텍스트 치환입니다.
- 타입과 scope 규칙이 약함: 함수 overload, parameter type checking, namespace와 같은 언어 기능을 그대로 제공하지 않습니다.
- 인자를 여러 번 평가할 수 있음:
SQUARE(i++)는i++를 두 번 치환하며, 이 예제는 unsequenced modification으로 undefined behavior가 됩니다. - 진단과 디버깅이 어려움: 오류가 확장된 토큰에서 나타나고 일반 함수처럼 단계별 호출을 추적하기 어렵습니다.
함수는 각 인자 표현식을 한 번씩 평가하고 C++의 type·scope 규칙을 따릅니다. preprocessing이 꼭 필요한 조건부 컴파일, 문자열화 또는 token 결합이 아니라면 일반 함수, constexpr 함수나 함수 template을 먼저 선택합니다.
후보 선정과 측정
실제 인라인화를 검토할 때는 inline 표기부터 늘리지 말고 다음 순서를 따릅니다.
- 실제 target과 release 최적화 옵션, LTO·PGO 설정, 대표 입력을 고정합니다.
- profiler로 hot call site를 찾습니다.
- compiler optimization remark나 생성 assembly에서 실제 치환 여부와 미치환 이유를 확인합니다.
- runtime과 함께 binary 크기, instruction-cache 지표를 비교합니다.
CANDIDATE · COST · EVIDENCE
인라인화 후보의 실제 이득은 속도와 코드 크기로 확인한다
함수 크기나 호출 형태 하나만으로 실제 인라인화 여부를 단정할 수 없습니다. optimizer가 보는 문맥과 release 설정을 맞춘 뒤 runtime, code size, cache 영향을 함께 확인합니다.
작고 뜨거우며 caller 문맥이 이익을 만든다
return a + b; 같은 짧은 본문이 hot path에서 반복되고, 상수 전파나 분기 제거처럼 치환 뒤의 추가 최적화가 기대되면 좋은 후보입니다. 본문이 보이거나 LTO가 정보를 가져올 수 있어야 합니다.
크고 차가운 함수는 코드 증가가 이익을 덮을 수 있다
큰 본문을 여러 호출 지점에 복제하면 binary와 instruction-cache 부담, 컴파일 시간, 디버깅 난도가 커질 수 있습니다. “루프 안 호출”이나 inline 표기만으로 hot하다고 가정하지 않습니다.
재귀·간접 호출·복잡한 제어 흐름도 문맥을 본다
- 재귀 호출을 무한히 펼칠 수는 없지만 비재귀 call site, 제한된 깊이 또는 일부 경로는 인라인화될 수 있습니다.
- 함수 포인터나 virtual 호출도 target이 밝혀지거나 devirtualize되면 직접 호출로 바뀌어 후보가 될 수 있습니다.
- 큰 루프나
switch는 비용을 높일 뿐 절대 금지 규칙은 아니며 partial inlining도 가능합니다. - 정상적으로 한
.cpp에 정의한 일반 함수도 같은 번역 단위의 호출이나 LTO 분석에서는 인라인화될 수 있습니다. 이는 inline 정의의 reachability 규칙을 대신하지 않습니다.
함수·template을 텍스트 치환과 혼동하지 않는다
함수는 scope와 type checking을 가지며 인자를 한 번씩 평가합니다. 반면 SQUARE(i++) 같은 매크로는 인자를 두 번 치환해 unsequenced modification 같은 undefined behavior를 만들 수 있습니다.
조건부 컴파일, 문자열화, token 결합처럼 preprocessing이 필요한 경우가 아니라면 일반 함수, constexpr 함수나 template을 먼저 선택합니다.
키워드가 아니라 증거로 유지 여부를 결정한다
비교 조건 고정
실제 target, release 최적화 옵션, LTO·PGO 설정과 대표 입력을 맞춘 기준 binary를 준비합니다.
hot call site 확인
profiler로 시간이 쓰이는 호출을 찾고 compiler optimization remark나 생성 assembly에서 치환·미치환 이유를 확인합니다.
속도와 크기를 함께 비교
runtime뿐 아니라 binary 크기와 instruction-cache 지표도 비교합니다. 개선이 재현되지 않으면 표기나 강제 attribute를 늘리지 않습니다.
판정 순서: 언어 계약을 먼저 맞춘 뒤 optimizer에 후보를 맡기고, profile과 최적화 기록으로 실제 병목에서 이득이 있었는지 확인합니다.
핵심은 두 문장으로 정리할 수 있습니다.
- 헤더의
inline정의는 정의 도달성과 ODR을 위한 언어 계약입니다. - 성능을 위한 실제 인라인화는 optimizer가 호출 지점별로 수행하고, 개발자는 측정으로 결과를 확인합니다.