분석 가능성

분석 가능성 (Analyzability)

정의되지 않은 동작(undefined behavior)은 실행 결과를 예측할 수 없어서 프로그램을 어렵게 만드는데요. C 표준의 선택적 확장인 분석 가능성(Analyzability)은 정의되지 않은 동작의 일부 형태가 낼 수 있는 결과를 제한해서, 그런 프로그램을 정적 분석(static analysis)으로 다루기 쉽게 만들어줘요.

출처: cppreference

본문

이것은 C 언어의 선택적 확장으로, 정의되지 않은 동작의 일부 형태를 실행할 때 나올 수 있는 결과의 범위를 제한해요. 그 덕분에 그런 프로그램에 대한 정적 분석의 효과가 좋아져요. 분석 가능성은 컴파일러가 사전 정의 매크로 상수 __STDC_ANALYZABLE__(C11)을 정의했을 때만 켜져 있다고 보장돼요.

컴파일러가 분석 가능성을 지원하면, 동작이 정의되지 않은 모든 언어·라이브러리 구성요소는 치명적(critical) 또는 제한적(bounded) 정의되지 않은 동작으로 더 세분화되고, 제한적 UB의 모든 경우의 동작은 아래에 명시된 대로 제한돼요.

치명적 정의되지 않은 동작 (Critical undefined behavior)

치명적 UB는 어떤 객체의 경계를 벗어나 메모리 쓰기나 volatile 메모리 읽기를 수행할 수 있는 정의되지 않은 동작이에요. 치명적 정의되지 않은 동작을 가진 프로그램은 보안 공격에 취약할 수 있어요.

다음의 정의되지 않은 동작만 치명적이에요.

  • 자신의 수명(lifetime)을 벗어난 객체에 접근 (예: 댕글링 포인터를 통한 접근)
  • 선언이 호환되지 않는 객체에 쓰기
  • 가리키는 함수의 타입과 호환되지 않는 타입의 함수 포인터를 통한 함수 호출
  • lvalue 표현식이 평가됐지만 객체를 지정하지 않는 경우
  • 문자열 리터럴을 수정하려는 시도
  • 유효하지 않은(널, 불확정 등) 또는 끝을 지나친 포인터 역참조
  • const가 아닌 포인터를 통한 const 객체의 수정
  • 유효하지 않은 인자로 표준 라이브러리 함수나 매크로 호출
  • 예상치 못한 인자 타입으로 가변 인자 표준 라이브러리 함수 호출 (예: 변환 지정자와 맞지 않는 타입의 인자를 printf에 넘기는 경우)
  • 호출 스코프 위쪽에 setjmp가 없는데 longjmp 호출, 스레드 경계를 넘는 longjmp, 또는 VM 타입의 스코프 안에서의 longjmp
  • free나 realloc으로 해제된 포인터의 어떤 사용
  • 어떤 문자열 또는 와이드 문자열 라이브러리 함수가 배열을 경계 밖으로 접근

제한적 정의되지 않은 동작 (Bounded undefined behavior)

제한적 UB는 불법 메모리 쓰기를 수행할 수 없는 정의되지 않은 동작이에요. trap을 일으키거나 불확정 값을 만들어내거나 저장할 수는 있어요.

치명적 목록에 없는 모든 정의되지 않은 동작은 제한적이에요. 예를 들면:

  • 멀티스레드 데이터 경합
  • 자동 저장 기간의 불확정 값을 사용
  • strict aliasing 위반
  • 정렬이 맞지 않는 객체 접근
  • 부호 있는 정수 오버플로
  • 순서가 정해지지 않은 부작용이 같은 스칼라를 수정하거나, 수정과 읽기를 동시에 하는 경우
  • 부동소수점-정수 또는 포인터-정수 변환 오버플로
  • 음수 또는 너무 큰 비트 수만큼의 비트 시프트
  • 0으로 정수 나눗셈
  • void 표현식 사용
  • 정확히 겹치지 않는 객체의 직접 대입 또는 memcpy
  • restrict 위반
  • 등등.. 치명적 목록에 없는 모든 정의되지 않은 동작

주의

제한적 정의되지 않은 동작은 특정 최적화를 끄기도 해요. 분석 가능성을 켜고 컴파일하면 소스 코드의 인과 관계(source-code causality)가 보존되는데, 그게 아니면 정의되지 않은 동작 때문에 깨질 수 있어요.

또 분석 가능성 확장은 구현-정의 동작의 한 형태로, trap이 발생했을 때 런타임 제약 처리기(runtime constraint handler)를 호출하는 것을 허용해요.

더 알아보기

  • 정의되지 않은 동작의 개념과 분류는 정의되지 않은 동작 문서에서 볼 수 있어요.
  • cppreference의 Analyzability 원문을 참고할 수 있어요.