PS/자주 하는 답변
Undefined Behavior가 있으면 무슨 일이 일어나도 이상하지 않습니다.
djm03178
2023. 4. 18. 10:34
C/C++이 어려운 이유 중 하나로 디버깅이 어렵다는 점을 들 수 있을 것입니다. 직접적으로 기계어로 번역되어 실행되는 특성상 코드에 어떤 문제점이 있어도 프로그래머에게 에러 내역을 알려주지 못하는 경우가 많습니다.
이 단점을 극단적으로 보여준다고 할 수 있는 것이 바로 undefined behavior입니다. 번역하면 미정의 동작으로, 그야말로 어떤 일이 벌어져도 이상하지 않게 만드는 코드상의 오류입니다.
Undefined behavior (UB)가 무엇이고, 어떤 종류가 있는지는 evenharder 님이 작성하신 C/C++의 undefined behavior 글을 참고하도록 하고, 이 글에서는 이 UB라는 녀석이 왜 무서운지만 간단하게 정리해 보겠습니다.
- UB가 발생하면 무슨 일이 일어나도 이상하지 않습니다. 농담처럼 UB가 일어나면 컴퓨터가 로봇 댄스를 춘다, 하드 디스크가 포맷된다 같은 말을 하곤 하지만, UB의 정의에 따르면 진짜로 그런 일이 일어나더라도 컴파일러에게는 잘못이 없습니다. UB가 바로 그런 것이기 때문입니다. 물론 이런 기상천외한 일은 벌어지려고 해도 대개 운영체제 차원의 보안에서 막히겠지만, 코드 자체는 무슨 짓을 하려고 시도할지 알 수조차 없습니다.
- UB는 자신의 존재를 알려주지 않습니다. 만약 UB가 있는데 segmentation fault가 뻥 터져줬다면 그건 너무나도 운이 좋고 너무나도 행복한 상황인 것입니다. 많은 경우 UB는 자기가 있는지조차 프로그래머가 알 수 없게 합니다.
- UB는 자신의 위치를 알려주지 않습니다. UB는 자신이 존재하는 코드 부분과 전혀 동떨어진 다른 곳에 있는 데에서 문제를 일으키곤 합니다.
- 같은 UB가 항상 같은 동작을 하지 않습니다. 흔히 "초기화되지 않은 변수에는 쓰레기값이 들어있다"라든가, "부호 있는 정수형의 최댓값을 넘어서면 최솟값으로 돌아온다"라든가, "배열의 범위를 넘어서면 런타임 에러가 난다" 등의 이야기를 많이 들어봤겠지만, 이것들은 엄밀히 말하면 모두 사실이 아닙니다. 이들은 모두 UB이며, 위에서 언급한 현상들은 모두 '많은 경우에' 그렇게 동작한다는 것뿐입니다. 실제로는 초기화되지 않은 변수를 딱 한 번 읽었을 뿐인데 무한 루프에 걸린다거나, 부호 있는 정수형의 최댓값을 넘어섰더니 갑자기 서로 모순되는 두 조건문을 동시에 통과하게 된다거나, 배열의 범위를 넘어섰더니 전혀 다른 변수의 값이 바뀌어있는 등의 온갖 시나리오가 모두 가능합니다.
- 같은 UB가 환경마다 다른 결과를 낼 수 있습니다. 같은 입력에 대해서도 서로 다른 환경에서는 서로 전혀 다른 결과가 나올 수 있습니다. 질문 게시판의 예시를 전부 넣어봐도 다 잘 나오는데도 불구하고 채점 서버에서는 예제조차 안 나올 수도 있습니다.
이처럼 UB는 프로그래머에게 큰 골칫덩이입니다. 똑같이 UB를 일으켜도 어떤 코드는 그대로 정답을 받고, 어떤 코드는 틀리고, 어떤 코드는 시간 초과, 메모리 초과, 런타임 에러를 랜덤으로 받는 등 어떤 판정을 받더라도 이상하지 않습니다. 따라서 C/C++로 문제를 푼다면 UB가 발생하지 않게끔 신경써서 안전한 코딩을 하는 것이 매우 중요함을 여러 번 강조해도 지나치지 않을 것입니다.