Skip to content

Instantly share code, notes, and snippets.

@simnalamburt
Last active August 20, 2023 15:48
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save simnalamburt/31e477072495be2bc5ab71b0507df8d1 to your computer and use it in GitHub Desktop.
Save simnalamburt/31e477072495be2bc5ab71b0507df8d1 to your computer and use it in GitHub Desktop.

NOTE: This article is written before C++17 release. Evaluation order of functions arguments is not UsB anymore. See P0145 for further details.

 

unspecified.cpp

이 문서는 제가 트위터에 냈던 문제의 해설입니다.

#include <vector>

using std::vector;

int main() {
  vector<int> a = { 1, 2, 3 };
  int& b = a[1];
  a.push_back(4);
  b = 0;
  // 이때 `a[1]` 의 값은?
}

스포주의

















알 수 없다 입니다. UsB가 제일 먼저 일어나고, 그거에 따라 UB가 일어날수도 있습니다.

해설

이 문제에는 함정이 두개있습니다.

  1. 벡터의 .size().capacity()와 같은 상황에서 .push_back()을 호출하면 기존 원소들에 달려있던 레퍼런스들은 전부 invalidate 됩니다. 그래서 아래 코드는 확실한 UB입니다.

    vector<int> a = { 1, 2, 3 };
    // a.size() == a.capacity() 라고 가정
    
    int& b = a[1];
    a.push_back(4);
    b = 0;
  2. 근데 C++ 표준은 .capacity()가 어떤 값이 되어야한다고 전혀 정의를 안해놨습니다. 대부분의 구현체들은 vector<int> a = { 1, 2, 3 }; 코드 직후에 .size().capacity()가 모두 3으로 같긴 하겠지만, 그냥 구현체가 그럴뿐이지 표준에 명시되지 않았습니다.

    심지어 C++ 표준은 .shrink_to_fit()을 호출한 뒤에 .capacity()의 값이 줄어들어야한다는것조차도 정의하지 않았습니다.

    그래서 이런식의 문제는 코드만 보고서는 어느때에 a.size() == a.capacity()를 만족할지 전혀 알수가 없습니다. 그래서 Unspecified 입니다.

UB? UsB? IDB?

Undefined Behavior, Unspecified Behavior, Implemenation-defined Behavior는 편의상 각각 UB, UsB, IDB로 줄여부릅니다. 그리고 세 용어의 정의는 C++ 표준 거의 첫페이지에 나와있습니다.

C++ 표준은 https://isocpp.org/std/the-standard 에서 표준제정 직전의 Working Draft를 받아서 보실 수 있으십니다.

N4296을 기준으로, UB, UsB, IDB 세 용어의 정의는 각각 아래와 같습니다.

  1. undefined behavior
    1.3.24 [defns.undefined]

    behavior for which this International Standard imposes no requirements

  2. unspecified behavior
    1.3.25 [defns.unspecified]

    behavior, for a well-formed program construct and correct data, that depends on the implementation

  3. implementation-defined behavior
    1.3.10 [defns.impl.defined]

    behavior, for a well-formed program construct and correct data, that depends on the implementation and that each implementation documents

A. UB

UB는 표준이 '이 동작은 어떠해야한다'고 정의하지 않은 경우입니다. UB의 제일 대표적인 경우가 Dereferencing of null pointer입니다.

int *ptr = nullptr;
*ptr = 100; // <- UB

두번째줄에서 세그폴트를 일으키고 죽든, 널포인터 익셉션 경고메세지를 띄우고 죽든, 죽지않고 진행하든, 컴퓨터를 포맷하든, 로봇혁명을 일으키든 모두 C++ 표준을 거스르지 않는 동작입니다.

당연히 UB에 의존해선 안됩니다. UB가 어떠한 sane한 값을 내놓는것처럼 보여도 보아선 안됩니다. 컴파일러가 기분좋은 결과를 들려줘도 들어선 안됩니다. UB가 믿음직해보여도 믿어선 안됩니다.

B. UsB

UsB는 표준이 '이 동작은 어떠하거나, 어떠하거나, 어떠해야한다' 이런식으로 선택지를 제공한 경우입니다. UsB의 제일 대표적인 사례는 (C++14 기준) 파라미터 eval 순서입니다.

function_call(foo(), bar());
  1. foo()가 먼저 호출되고 그 다음 bar()가 호출됨 - OK!
  2. bar()가 먼저 호출되고 그 다음 foo()가 호출됨 - OK!
  3. foo()만 두번 부름 - 표준에 어긋남
  4. 일본을 공격함 - 표준에 어긋남

구현체가 고를 수 있는 선택지가 유한하지 않을수도 있습니다. 방금 여러분이 푸신 문제가 그 경우에 해당합니다.

vector<int> a = { 1, 2, 3 };
// a.capacity() 의 값은?
  1. 1 - 표준에 어긋남
  2. 2 - 표준에 어긋남
  3. 3 - OK!
  4. 4 - OK!
  5. 5 - OK!
  6. ...

이경우 C++ 표준은 a.capacity()의 값이 a.size()보다 작아서는 안된다고만 써놨지 어떠한 값을 가져야한다고 써놓지는 않았습니다. 그래서 UsB 입니다.

대부분의경우 UsB에 의존하여 프로그래밍하면 좋지 않습니다.

C. IDB

IDB는 UsB의 하위집합입니다. C++ 표준은 UsB중 몇몇개를 IDB로 지정하여 구현체가 어느 선택지를 골랐는지 명확하게 문서화하여, 프로그래머가 그 동작에 의존할 수 있도록 할것을 명시하고있습니다.

제일 대표적인 IDB가 size_t의 크기입니다. C++ 표준에는 size_t의 크기가 그냥 "large enough to contain the size in bytes of any object" 라고만 써있지 어떠해야한다고는 써있지 않습니다. 하지만 동시에 "implementation-defined"임을 명확하게 명시하고있고, 모든 컴파일러 구현체는 size_t의 크기가 얼마인지 전부 명확하게 써야합니다.

또다른 IDB가 #pragma입니다. C++ 표준에는 Pragma directive라고 해서 #pragma blabla blabla 를 코드 중간에 쓸 수 있음을 명확하게 명시하였지만, 그 동작이 어떠할지는 구현체에 맡겼습니다 (behave in an implementation-defined manner)

UsB와는 다르게, IDB는 필요할때엔 사용해야합니다. size_t의 크기가 얼마인지도 가정하지 못한다면 프로그래밍하기 너무 힘들겠죠? 그래도 코드가 portable하게 동작하길 원한다면 IDB에도 의존하지 않는것이 좋습니다. 예를들어 32비트와 64비트 모두에서 올바르게 컴파일되길 원한다면, size_t의 크기가 몇이라고 가정하기보다 sizeof(size_t)에 의존하는것이 좋겠죠.

C++에 IDB는 몇개 없습니다. C++ 표준 맨 마지막장에 컴파일러 만드는 사람들 보기 편하라고 IDB만 추려놓은 목록이 있는데, 한번 보시면 일반 프로그래머가 C++을 쓰다가 IDB에 의존할이 별로 없음을 아시게될겁니다.

References
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment