| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 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 | 29 | 30 |
| 31 |
- 오늘의에러
- 탐색기법
- 얌얌코딩
- 언리얼
- 내가해냄
- 코딩테스트
- 언리얼로그
- 게임개발
- 재귀함수
- 구조체포인터
- 코딩
- 자료구조
- 링크드리스트
- 백준
- UE5
- dfs
- 게임프로그래밍
- permutation
- 개발
- TPS
- C++
- c++자료구조
- 구조체
- fstring
- unreal
- 연산자오버로딩
- 커스텀로그
- 미라클모닝
- 개발자
- 프로그래밍
- Today
- Total
All is well
[오늘의에러] error C4840: variadic 함수의 인수로서 'FString' 클래스를 이식 불가능하게 사용했습니다. (feat. `UE_LOG`에서 `FString` 사용 시 에러 발생) 본문
[오늘의에러] error C4840: variadic 함수의 인수로서 'FString' 클래스를 이식 불가능하게 사용했습니다. (feat. `UE_LOG`에서 `FString` 사용 시 에러 발생)
D0YUN 2025. 3. 1. 12:44
오늘의 에러
TPS_GameModeBase.cpp(10): error C4840: variadic 함수의 인수로서 'FString' 클래스를 이식 불가능하게 사용했습니다.
UE에서 Custom Log를 만들고 사용하는 과정에서 다음과 같은 에러를 만났다
에러 발생 상황

Custom Log Category 및 Custom Log는 `TPSMAR.h`에서 작성하였다.

로그 사용을 위해 `TPSMAR.cpp`에 Log Category를 등록하였고


`TPS_GameModeBase`에서 사용하였다.
빨간색으로 표시한 부분에서 에러가 발생하였다.
에러 분석
TPS_GameModeBase.cpp(10): error C4840: variadic 함수의 인수로서 'FString' 클래스를 이식 불가능하게 사용했습니다.
해당 에러는 가변 인자(variadic arguments) 함수에서 FString을 직접 전달할 때 발생하는 오류라고 한다.
에러 원인

결론부터 말하면 ` PRINT_LOG(fmt, ...)`의 `FString::Printf`를 이용하여 포맷 문자열을 전달하는 부분이 잘못되었다.
`ATPS_GameModeBase` 생성자에서 위 매크로를 다음과 같이 호출했다.
PRINT_LOG(TEXT("My Log : %s"), TEXT("TPS Project!"));
이를 확장하면 다음과 같다.
UE_LOG(TPS, Warning, TEXT("%s %s"), *CALLINFO, FString::Printf(TEXT("My Log : %s"), TEXT("TPS Project!")));
문제는 `FString::Printf(TEXT("My Log : %s"), TEXT("TPS Project!"))` 부분이다.
`UE_LOG` 매크로는 내부적으로 `TCHAR*` 형을 인자로 요구한다.
그런데 `FString::Printf`는 `TCHAR*`이 아닌 `FString`을 반환한다.
`FString`을 직접 넣으면 변환 과정에서 호환되지 않는 타입 문제가 생겨 오류가 발생하는 것이었다.
해결 방법 1 : `FString::Printf()`의 결과를 `*` 연산자로 변환
가장 간단한 방법이다. `FString::Printf()`의 결과를 `*` 연산자를 이용하여 `TCHAR*`로 형 변환을 진행하는 것이다.
#define PRINT_LOG(fmt, ...) UE_LOG(TPS, Warning, TEXT("%s %s"), *CALLINFO, *FString::Printf(fmt, ##__VA_ARGS__))

이렇게 수정하면 `FString::Printf()`의 결과가 `TCHAR*`이 되어 `UE_LOG`와 호환되게 된다.
해결 방법 2 : `FString::Printf() ` 없이 직접 `UE_LOG` 사용
#define PRINT_LOG(fmt, ...) UE_LOG(TPS, Warning, TEXT("%s ") fmt, *CALLINFO, ##__VA_ARGS__)
이렇게 수정하면 `FString::Printf()` 없이도 바로 사용할 수 있다.

그런데 `TEXT("%s ") fmt` 이 어떤 원리로 가능한 건지 궁금해졌다.
`TEXT("%s ") fmt` 문법이 가능한 이유
: C++의 매크로 확장과 문자열 포맷팅 방식을 활용하여 가능하다.
매크로 내에서 문자열 결합
C++ 매크로에서 문자열 리터럴을 직접 결합하면 컴파일러가 자동으로 하나의 문자열로 병합한다.
#define EXAMPLE "Hello " "World!"
위 코드는 컴파일 시 `EXAMPLE`이 ``"Hello World!"`로 확장된다.
`TEXT("%s ") fmt`을 통해 살펴보는 매크로 내 문자열 결합
#define PRINT_LOG(fmt, ...) UE_LOG(TPS, Warning, TEXT("%s ") fmt, *CALLINFO, ##__VA_ARGS__)
위 매크로를
PRINT_LOG(TEXT("My Log : %s"), TEXT("TPS Project!"));
다음과 같이 호출하면
UE_LOG(TPS, Warning, TEXT("%s My Log : %s"), *CALLINFO, TEXT("TPS Project!"));
`%s `에서 `CALLINFO`를 출력하고, 그 뒤에 사용자가 넣은 문자열 포맷(`fmt`)을 그대로 출력하는 형태로 확장된다.
즉, `UE_LOG`의 가변 인자(`##__VA_ARGS__`)와 자연스럽게 호환이 된다는 것이고, 이는
`PRINT_LOG` 매크로 속`TEXT("%s ") fmt`이 `fmt`이 `TEXT("%s ")`와 자동으로 결합 돼 하나의 문자열로 처리되는 것을 의미한다.
성능 비교 - 방법 1 vs 방법 2
방법 1 : `FString::Printf` 활용
#define PRINT_LOG(fmt, ...) UE_LOG(TPS, Warning, TEXT("%s %s"), *CALLINFO, *FString::Printf(fmt, ##__VA_ARGS__))
방법 1의 `FString::Printf(fmt, ##__VA_ARGS__)`는 새로운 `FString` 객체를 생성하여 반환하기 때문에 `*` 연산자를 사용해야 한다.
즉, 불필요한 문자열 객체 생성이 발생하기 때문에 성능 저하 가능성이 있다.
방법 2 : `UE_LOG` 직접 활용
#define PRINT_LOG(fmt, ...) UE_LOG(TPS, Warning, TEXT("%s ") fmt, *CALLINFO, ##__VA_ARGS__)
방법 2는 `fmt`이 직접 확장되므로 `FString::Printf()`를 사용할 필요가 없고, `UE_LOG`의 가변 인자를 그대로 활용하므로 더 효율적이다.

`UE_LOG`는 인자로 `TCHAR*`를 받는다는 것 명심하자!