여러분이 방금 틀렸습니다
를 받았던 문제의 코너 케이스를 고쳤다고 해봅시다.
들뜬 마음에 점검도 안하고 바로 백준에 코드를 제출해버립니다!
void AddEdge(int v1, int v2)
{
adjacencyList[v1].push_back(v2);
adjacencyList[v2].push_back(v1);
// 어라? 이거 지웠어야 하는데 그냥 제출해버렸네...
printf("[DEBUG] Add edge (%d, %d)\n", v1, v2);
}
아... 근데 깜빡하고 디버깅하면서 출력했던 문자열을 안 지우고 제출해서 또 틀렸습니다
를 받아버렸습니다...
깜빡하지 않았다고 하더라도, 디버깅용 문자열을 여기저기서 출력했다면 그걸 일일히 찾아서 지우는 것도 꽤 귀찮은 일입니다.
아... 그냥 소스 수정 안하고 제출해도 디버깅용 문자열 출력이 알아서 없어지면 좋을텐데...
바로 그 방법을 소개합니다.
백준 채점기의 `ONLINE_JUDGE` 매크로 이용하기
백준 C/C++ 채점기의 컴파일 옵션을 살펴봅시다.
C++17
언어 번호: 84
컴파일: g++ Main.cc -o Main -O2 -Wall -lm -static -std=gnu++17 -DONLINE_JUDGE -DBOJ
C11
언어 번호: 75
컴파일: gcc Main.c -o Main -O2 -Wall -lm -static -std=gnu11 -DONLINE_JUDGE -DBOJ
-DONLINE_JUDGE -DBOJ
옵션이 들어간 게 보이시나요?
저 두 옵션에 의해 ONLINE_JUDGE
와 BOJ
라는 매크로가 정의되게 됩니다.
저걸 미리 정의된 매크로(Predefined Macros)라고 부릅니다.
쉽게 생각하면 여러분이 백준에 제출한 소스 코드 상단에
#define ONLINE_JUDGE
#define BOJ
가 들어간다고 생각할 수 있죠.
위 두 매크로가 백준 채점기에서만 정의된다는 걸 이용해, 저 매크로가 정의되지 않았을 때에만 디버깅용 출력을 출력하면 됩니다.
전처리기의 조건부 컴파일 기능을 활용하면 쉽게 이걸 구현할 수 있습니다.
void AddEdge(int v1, int v2)
{
adjacencyList[v1].push_back(v2);
adjacencyList[v2].push_back(v1);
// 조건부 컴파일: 로컬에서는 컴파일되지만, 백준 채점기에선 지워지고 컴파일된다.
#ifndef ONLINE_JUDGE
printf("[DEBUG] Add edge (%d, %d)\n", v1, v2);
#endif
}
모든 디버깅 출력 라인을 위와 같이 #ifndef ONLINE_JUDGE ~ #endif
또는 #ifndef BOJ ~ #endif
로 감싸주게 되면,
로컬 환경에선 디버깅 라인이 컴파일이 되고, 백준 채점기 환경에선 디버깅 라인이 삭제되고 컴파일이 됩니다.
즉, 디버깅 출력을 삭제할 것 없이 그대로 제출해도 된다는 말이죠.
사용 예시
제가 실제 제출한 예시입니다. (보시려면 2606번: 바이러스를 푸셨어야 합니다.)
전 이걸 약간 응용해서, ONLINE_JUDGE
가 정의되면 NDEBUG
가 정의되도록 하고,
#ifdef ONLINE_JUDGE
#define NDEBUG
#endif
모든 디버깅 출력 라인을 #ifndef NDEBUG ~ #endif
로 감쌌습니다.
왜 굳이 NDEBUG
를 한번 거쳐가냐면, cassert 와 assert.h 표준에서 NDEBUG
가 정의되면 assert(x)
문장을 무효화하거든요.
저렇게 하면 assert(x)
로 디버깅 한것도 백준 채점기에 들어가지 않아서, 실행 시간 이득을 노릴 수 있습니다. (미미하지만.)
(당연히 채점기에서 고의적으로 assert(x)
로 뭔가 체크해 볼 경우에는 위 방법을 쓰면 안되겠죠.)
그리고 main()
함수 제일 윗줄에 다음과 같이 써 줬습니다.
#define FAST_IO std::ios::sync_with_stdio(false); std::cin.tie(nullptr);
// ...
int main()
{
#ifndef NDEBUG
std::cout << "[DEBUG] Debug enabled!\n";
#else
FAST_IO;
#endif
// ...
}
위와 같이 해서 로컬 환경에서 돌릴 땐 FAST_IO
(빠른 입출력)이 꺼지게 하고, 제출할 때만 활성화되게 했습니다.
저렇게 하면 디버깅용 라인에 한해서만 printf()
와 std::cout
을 섞어 써도 됩니다.
또한, 로컬 환경에서는 [DEBUG] Debug enabled!
를 띄워 줘서 디버깅이 켜졌다는 걸 알립니다. (이건 사족이긴 한데 나쁠 건 없죠)
debug(x) 한 줄만 써서 조건부 출력
좋아요, 다 좋은데, #ifndef BOJ ~ #endif
로 디버깅 라인을 일일히 감싸는 것도 솔직히 귀찮잖아요?
대부분의 경우 특정 시점의 변수의 값만 출력하면 되는데, 그걸 3줄씩이나 쓰는건 손이 아프단 말이죠.
이 글의 debug(x) 매크로 함수를 cassert
의 조건부 컴파일 방법론과 응용하면,
debug(x);
만 써도 x
값 디버깅 출력을 로컬 환경에서만 컴파일 되도록 할 수 있습니다.
#ifdef BOJ
#define debug(x) ((void)0)
#else
#define debug(x) std::cout << "[Debug] " << #x << " is " << x << '\n'
#endif
이러면 debug(x);
는 굳이 #ifndef BOJ ~ #endif
로 감싸지 않아도 백준 채점기에선 알아서 출력이 안 되게 됩니다.
백준 채점기에선 무의미한 ((void)0)
으로 변환될테니까요.
이것까지 사용한, 조금 더 복잡한 예시 (이건 16236번: 아기 상어를 풀어야 보입니다.)
응용
이 외에도 조건부 컴파일을 잘 응용하면 좋을 만한 상황이 꽤 많습니다.
대표적으로, 로컬 환경에서 테스트할때는 내가 입력한 숫자랑 출력되는 숫자가 섞여 나와서 구분이 어려운데,
조건부 컴파일을 이용해 로컬 환경에서만 출력문 앞에 [OUTPUT]
을 붙인다던가 하는 게 가능하죠.
뭐 이런 응용은 직접 구현해보시면 좋을 것 같네요.
조건부 컴파일의 일반적인 활용
꼭 백준 문제풀이만이 아니더라도 C/C++ 조건부 컴파일을 잘 알아두면 좋습니다.
대표적으로 크로스 플랫폼 대응하는 코드를 짤 때 WIN32
, MACOS
나 UNIX
같은 매크로로 OS-specific 한 코드를 조건부 컴파일 할 수 있겠죠.
제가 아는 어떤 사람은 매크로 하나로 수많은 전역 변수들 앞에 static
을 뗐다 붙였다 하는 식으로 응용하더군요.
댓글 (11개) 댓글 쓰기
adxx 2년 전
어마어마 하네요
copyrat90 2년 전
감사합니다.
kim4969 2년 전
오호 유익합니다. Pre-defined Compiler Macros 입니다.
copyrat90 2년 전
오, 정확한 명칭 알려주셔서 감사합니다. 본문에 추가하겠습니다.
dohoon 2년 전
우와 debug(x) 반갑네요 ㅎㅎ 참고로 #define debug(x) 라고만 적고 뒤에 안 적으면 ((void)0)과 같은 효과를 낼 수 있는 것 같아요.
조건부 컴파일의 일반적인 활용> 저는: on - #define el '\n' off - #define el endl 이렇게 쓰고 있는데, 이 부분의 장점은 여러 테스트 케이스가 들어오는 문제들에 대해서 하나씩 interactive하게 확인할 수 있다는 겁니다.
참고로, 몇몇 judge들은 ONLINE_JUDGE가 없는 경우도 있어서 컴파일 명령어에 -DLOCAL을 넣는 것도 유용할 것 같아요. (앳코더도 아마 바뀌었는지 모르겠지만 안되는 것으로 알고 있어요)
유익한 글 감사합니다! 이런 매크로들 처음에 찾을 때 헤맸는데 필요한 것들이 다 적혀 있어서 너무 좋네요 :)
copyrat90 2년 전
-DLOCAL 좋은 접근 방법이네요. 매크로 없는 저지에서 써먹기 좋겠군요.
뒤에
((void)0)
를 안 적어도 된다라... 흥미롭네요. 표준cassert
에선 왜((void)0)
를 적었는지 궁금해지는군요. 뭔가 특수한 상황이 있는걸까요?jry9913 2년 전
제 것도 공유합니다. -DLOCAL을 사용했어요
copyrat90 2년 전
와, C++17 의
if constexpr
쓰니 아주 깔끔해지네요. 공유 감사합니다.copyrat90 2년 전
('22. 02. 09 업뎃) 요즘에는 C++17 의
if constexpr
과 가변인자 매크로(__VA_ARGS__
) 응용해서 이렇게 쓰고 있습니다. 답을 출력할 때는 cout 이든, printf 든 하나로만 해야 합니다.위 코드는
if constexpr
때문에 C++17 이상에서만 동작하긴 하는데,#ifndef ~ #endif
로 쉽게 고칠 수 있을 겁니다. 본문도 좀 업뎃이 필요해보이긴 하는데, 근본적으로 내용이 달라진 건 아니고, 자잘한 편의성 개선만 있어서 글 고치기가 애매하네요.euphoric_n 2년 전
이거 보고 나서
쓰다가 ONLINE_JUDGE에 오타내서 생고생했네요
다른분들은 조심하세요
junah 10달 전
위와 같은 방식으로 scanf, printf 쓰시는 분들도 쉽게 활용할 수 있습니다.