# C 프로그래밍 언어 퀴즈

> Clean Markdown view of GeekNews topic #29776. Use the original source for factual precision when an external source URL is present.

## Metadata

- GeekNews HTML: [https://news.hada.io/topic?id=29776](https://news.hada.io/topic?id=29776)
- GeekNews Markdown: [https://news.hada.io/topic/29776.md](https://news.hada.io/topic/29776.md)
- Type: GN+
- Author: [neo](https://news.hada.io/@neo)
- Published: 2026-05-23T09:15:51+09:00
- Updated: 2026-05-23T09:15:51+09:00
- Original source: [stefansf.de](https://stefansf.de/c-quiz/)
- Points: 1
- Comments: 1

## Topic Body

- **C 언어 규칙**은 포인터 비교, 별칭, 널 포인터, 초기화되지 않은 값처럼 단순해 보이는 코드도 정의되지 않은 동작으로 만들 수 있음
- 정수 상수와 `sizeof`, 문자 상수, `uint8_t` 산술은 **타입 선택과 정수 승격** 때문에 플랫폼·표기·중간 대입 위치에 따라 결과가 달라질 수 있음
- 함수 선언의 `foo()`와 `foo(void)`, 프로토타입 부재, 기본 인자 승격, 반환값 없는 함수는 **C와 C++** 에서 합법성이나 동작이 다르게 갈림
- 배열은 포인터가 아니며, 배열 매개변수는 포인터로 조정되고, `a`, `&a`, `&a[0]`는 같은 주소라도 **타입이 달라** 교환해 쓸 수 없음
- 연산자 우선순위와 평가 순서는 별개이며, `switch` 본문 구조와 임시 객체 수명까지 포함해 **표준 문구**가 실제 실행 결과를 좌우함

---

### 정의되지 않은 동작과 포인터 규칙
- ## 포인터 비교와 엄격한 별칭 규칙
  - 같은 타입의 포인터 `p`와 `q`가 같은 주소를 가리켜도, 서로 다른 객체에서 유래했고 같은 aggregate 또는 union 객체의 일부가 아니라면 `p == q` 비교는 **정의되지 않은 동작**이 될 수 있음
  - 포인터가 단순한 숫자 주소보다 더 추상적이라는 점은 [관련 글](https://stefansf.de/post/pointers-are-more-abstract-than-you-might-expect/)에서 이어짐
  - `int` 객체를 `short` lvalue로 접근하면 **strict aliasing** 규칙에 따라 정의되지 않은 동작이 됨
  - `unsigned char` 포인터는 예외적으로 어떤 객체든 별칭(alias)할 수 있어, `int` 객체를 `unsigned char` lvalue로 접근하는 것은 합법임
  - `unsigned char`는 패딩 비트와 trap representation이 없다는 보장이 있으며, C11부터는 `signed char`도 패딩 비트가 없다고 보장됨
  - 타입 기반 별칭 분석은 [관련 글](https://stefansf.de/post/type-based-alias-analysis/)에서 다룸
- ## 널 포인터와 포인터 표현
  - 널 포인터의 비트 표현이 반드시 모든 비트 0일 필요는 없음
  - C 표준은 **null pointer constant**를 정의하지만, 실행 시점의 널 포인터 표현이나 일반 포인터 표현은 정의하지 않음
  - Symbolics Lisp Machine 3600은 숫자 포인터 대신 `<array-object, index>` 형태의 튜플을 사용하며, 널 포인터 표현은 `<nil, 0>`임
  - 추가 예시는 [clc FAQ 5.17](http://c-faq.com/null/machexamp.html)에 있음
  - 상수 `0`은 문맥에 따라 정수 또는 널 포인터가 되며, `(void *)0`은 널 포인터로 평가됨
  - 표현식 `e`가 `0`으로 평가된다고 해서 `(void *)e`가 널 포인터가 된다는 보장은 없음
  - 오직 **null pointer constant**가 포인터 타입으로 변환될 때만 널 포인터와 같다고 보장됨
  - 널 포인터에 대한 산술은 정의되지 않은 동작이므로, `e`가 널 포인터라도 `e + 0`이 널 포인터라는 보장은 없음
- ## 초기화되지 않은 값
  - 초기화되지 않은 자동 저장 기간 객체를 읽을 때, 그 객체가 `register` 저장 클래스가 될 수 있고 주소가 한 번도 취해지지 않았다면 C11 § 6.3.2.1 ¶ 2에 따라 **정의되지 않은 동작**이 됨
  - 이 규칙은 [DR338](http://www.open-std.org/jtc1/sc22/wg14/www/docs/dr_338.htm)에서 다루는 Intel Itanium 아키텍처와 연결됨
  - Itanium의 일반 정수 레지스터는 64비트와 trap bit 하나를 가지며, 이 trap bit는 레지스터가 초기화되었는지 나타내는 `NaT`(not-a-thing)임
  - 변수의 주소를 취하면 해당 조건은 사라지지만, 값은 indeterminate이며 trap representation 또는 unspecified value가 될 수 있음
  - trap representation을 읽으면 C11 § 6.2.6.1 ¶ 5에 따라 정의되지 않은 동작이 됨
  - unspecified value라면 `x != x`의 결과도 `true` 또는 `false`가 될 수 있고, `int x`가 unspecified이면 `x *= 0` 이후에도 `x`가 0이라는 보장이 없음
  - indeterminate와 unspecified value는 [DR260](http://www.open-std.org/jtc1/sc22/wg14/www/docs/dr_260.htm), [DR451](http://www.open-std.org/Jtc1/sc22/WG14/www/docs/dr_451.htm), [N1793](http://www.open-std.org/Jtc1/sc22/wg14/www/docs/n1793.pdf), [N1818](http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1818.pdf), [N2012](http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2012.htm#clarifying-the-c-memory-object-model-uninitialised-values), [N2013](http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2013.pdf), [N2221](http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2221.htm)에서 논의됨
- ## `unsigned char`와 `memcpy`
  - `unsigned char` 타입은 C11 § 6.2.6.1 ¶ 3에 따라 trap representation이 없으므로 초기값은 unspecified임
  - StackOverflow의 C 위원회 멤버 [답변](https://stackoverflow.com/questions/11962457/why-is-using-an-uninitialized-variable-undefined-behavior/11965368#11965368)은 표준 라이브러리 함수 `memcpy` 호출 뒤 `x`의 값이 specified가 되어야 하며, 이 해석에서는 `x != x`가 `false`가 된다고 봄
  - C 표준에서 이를 뒷받침하는 근거는 명확하지 않고, [DR451](http://www.open-std.org/jtc1/sc22/wg14/www/docs/dr_451.htm)의 위원회 응답은 indeterminate value에 라이브러리 함수를 사용하면 정의되지 않은 동작이라고 해 이 해석과 충돌함
  - 이 질문은 열린 상태로 남아 있으며, 추가 논의는 [Uninitialized Reads](https://queue.acm.org/detail.cfm?id=3041020)에 있음

### 정수 상수, 승격, `sizeof`
- ## 정수 상수의 표기와 타입
  - 접미사가 없는 **10진 정수 상수**는 항상 signed 타입 목록에서 선택되지만, 8진·16진 상수는 signed 또는 unsigned 타입이 될 수 있음
  - C17 § 6.4.4.1에 따라 정수 상수의 타입은 해당 값을 표현할 수 있는 목록의 첫 번째 타입으로 정해짐
  - 접미사가 없을 때 10진 상수는 `int`, `long int`, `long long int` 순서이고, 8진·16진 상수는 `int`, `unsigned int`, `long int`, `unsigned long int`, `long long int`, `unsigned long long int` 순서임
  - `INT_MAX+1`부터 `UINT_MAX` 사이의 상수는 10진인지 16진인지에 따라 타입이 달라질 수 있고, 가변 인자 함수 호출처럼 ABI에 민감한 코드에서 차이를 만들 수 있음
  - [Arm 32-bit architecture ABI](https://github.com/ARM-software/abi-aa/blob/60a8eb8c55e999d74dac5e368fc9d7e36e38dda4/aapcs32/aapcs32.rst)에서는 `int`와 `long`이 32비트로 레지스터 하나에 전달되고, `long long`은 64비트로 레지스터 두 개에 전달됨
  - `int`가 32비트인 플랫폼에서는 `-1 < 0x8000`이 `true`가 되고, `int`가 16비트인 플랫폼에서는 `false`가 되어 **이식성 문제**가 생길 수 있음
  - generic selection, C++ 오버로드 함수, `sizeof(0x80000000) == sizeof(2147483648)` 같은 표현식에서도 상수 타입 차이가 결과를 바꿀 수 있음
- ## `sizeof(int) > -1`
  - `sizeof` 연산자는 `size_t` 타입의 unsigned integer를 반환함
  - C11 § 6.3.1.8의 usual arithmetic conversions에 따라 signed 피연산자가 unsigned 피연산자보다 낮은 rank를 가지면 같은 rank의 unsigned 타입으로 변환됨
  - `-1`에 해당하는 signed integer는 unsigned로 변환될 때 해당 rank의 최대 unsigned integer가 됨
  - 따라서 `sizeof(int) > -1`은 항상 `false`로 평가됨
- ## 문자 상수의 타입
  - C에서 문자 상수는 C11 § 6.4.4.4 ¶ 10에 따라 `int` 타입임
  - 따라서 `sizeof(char) == sizeof('x')`가 항상 `true`라는 보장은 없고, `sizeof(int) == sizeof('x')`만 보장됨
  - integer character constant는 하나 이상의 multibyte character 시퀀스일 수 있어 `'abc'`도 유효하며, 그 표현은 구현 정의임
  - 단일 문자를 포함한 integer character constant의 값은 같은 단일 문자를 나타내는 `char` 타입 객체의 정수 표현과 같음
- ## `uint8_t` 산술과 나눗셈
  - `a`, `b`, `c`가 읽기 전에 초기화되어 있어도, 정수 승격과 중간 대입 위치 때문에 `x`와 `z`의 값이 다를 수 있음
  - 각 변수 값은 `int` 크기로 승격된 뒤 덧셈과 나눗셈이 수행되고, 각 대입 결과는 해당 변수 타입으로 truncate되어 저장됨
  - 예를 들어 `a=255`, `b=1`, `c=2`이면 `x`는 `((255 + 1) / 2) % 256 = 128`이 됨
  - 중간 변수 `y`는 `(255 + 1) % 256 = 0`이 되고, 그 뒤 `z`는 `(0 / 2) % 256 = 0`이 되어 `128 != 0`임
  - unsigned integer overflow는 정의된 동작임
  - 모듈로 연산은 덧셈에 대해 분배되므로 나눗셈을 덧셈으로 바꾸면 `x`와 `z`는 항상 같음
  - 첫 대입을 `uint8_t x = ((uint8_t)(a + b)) / c;`로 바꿔도 `x`와 `z`는 항상 같아짐
- ## `const` 변수와 variable length array
  - `const`로 한정된 변수 `n`과 `m`을 배열 크기로 사용해도, 이들은 C의 **integer constant expression**이 아님
  - C11 § 6.6 ¶ 6에서 integer constant expression은 integer constant, enumeration constant, character constant, 결과가 integer constant인 `sizeof`, `_Alignof`, cast의 즉시 피연산자인 floating constant 등으로 제한됨
  - 배열 크기 표현식이 integer constant expression이 아니면 C11 § 6.7.6.2 ¶ 4에 따라 variable length array가 됨
  - variable length array는 file scope에서 허용되지 않아 전역 배열 `x`가 있는 compilation unit은 컴파일되지 않음
  - block scope에서는 variable length array가 허용되므로 지역 배열 `y`가 있는 compilation unit은 컴파일될 수 있음
  - variable length array는 구현이 지원하지 않아도 되는 conditional feature이므로, 이를 지원하지 않는 컴파일러에서는 block scope 예도 컴파일되지 않을 수 있음
  - C++에서는 두 compilation unit이 모두 컴파일되며, C++에는 variable length array 개념이 없어 `y`는 42개 원소를 가진 일반 배열로 컴파일됨

### 함수 선언, 반환값, linkage
- ## `foo()`와 `foo(void)`
  - `foo()` 형태의 함수 선언은 인자 개수와 타입을 모르는 함수를 선언하고, `foo(void)`는 인자가 없는 **nullary function**을 선언함
  - 이 차이는 [함수 선언·정의·프로토타입 관련 글](https://stefansf.de/post/declaring-defining-and-prototyping-functions/)에서 다룸
  - 인자 목록이 없는 선언은 함수 이름만 도입하고 인자 수와 타입을 정의하지 않기 때문에, 뒤의 함수 정의와 결합해 합법일 수 있음
  - 프로토타입 없이 함수가 호출되면 **default argument promotions**가 적용되어 `float`는 `double`로 승격됨
  - 승격 후의 함수 타입이 실제 함수 정의의 타입과 호환되지 않으면 선언과 정의의 조합은 유효하지 않음
  - 선언이 없는 함수 호출은 C에서는 암시적 함수가 허용되어 컴파일될 수 있지만, C++에서는 컴파일 오류임
  - 선언 없이 `bar(42)` 같은 호출을 하면 정수 인자 승격이 적용되어 `42`는 `int`로 표현되므로, `bar`가 어떤 반환 타입 `T`에 대해 `T (*)(int)`와 호환되지 않으면 정의되지 않은 동작이 됨
- ## 값을 반환하지 않는 value-returning 함수
  - 반환 타입이 `int`인 함수가 값을 반환하지 않아도, C에서는 호출 결과 값을 사용하지 않는 한 합법일 수 있음
  - K&R C에는 `void` 타입이 없었고 타입을 생략하면 기본 타입 `int`가 가정되었기 때문에, 값을 반환하지 않는 함수와 암시적 `int` 규칙이 역사적으로 연결되어 있음
  - 암시적 `int` 규칙은 C99에서 폐지되었으며, 관련 논의는 [N661](http://www.open-std.org/jtc1/sc22/wg14/www/docs/text/n661.txt)과 [C99 rationale](http://www.open-std.org/jtc1/sc22/wg14/www/C99RationaleV5.10.pdf)에 있음
  - C17 § 6.9.1 ¶ 12는 함수 끝의 `}`에 도달했고 호출자가 함수 호출 값을 사용하면 **정의되지 않은 동작**이라고 규정함
  - C++98 § 6.6.3 ¶ 2에서는 value-returning 함수의 끝으로 흘러나가는 것 자체가 값 없는 `return`과 같고, value-returning 함수에서는 정의되지 않은 동작이 됨
  - C++ 컴파일러는 어떤 분기에서 `abort_program()`이 종료하는지 일반적으로 증명할 수 없기 때문에 이런 경우 오류가 아니라 진단만 낼 수 있음
- ## linkage와 `extern`
  - 이전 선언이 보이는 스코프에서 `extern`으로 같은 식별자를 다시 선언하면, 나중 선언의 linkage는 이전 선언의 linkage와 같음
  - C17 § 6.2.2 ¶ 4는 이전 선언이 internal 또는 external linkage를 지정했다면 이후 `extern` 선언도 같은 linkage를 갖는다고 규정함
  - 이전 선언이 보이지 않거나 이전 선언에 linkage가 없으면 `extern` 식별자는 external linkage를 가짐
  - 반대 순서의 선언 조합은 정의되지 않은 동작이 될 수 있으며, GCC와 Clang이 이를 잡아냄

### 한정자와 불완전 타입
- ## 함수 매개변수의 `const`
  - 함수 선언에서 매개변수 `x`가 `const`로 한정되고 함수 정의에서는 그렇지 않으며 함수 본문에서 `x`에 값을 써도 합법임
  - C11 § 6.7.6.3 ¶ 15에 따라 함수 매개변수 타입 호환성과 composite type을 판단할 때, qualified type으로 선언된 각 매개변수는 **unqualified version**으로 취급됨
  - 같은 주제는 [DR040](http://www.open-std.org/jtc1/sc22/wg14/www/docs/dr_040.html)에서도 다뤄짐
- ## 함수 반환 타입의 `const`
  - 함수 정의의 반환 타입만 `const`로 한정되고 선언은 그렇지 않은 경우의 정답은 단순히 맞거나 틀리다고 보기 어려움
  - 전체적인 합의는 rvalue의 한정자는 무시되어야 한다는 쪽이지만, C11까지의 표준 문구는 이를 명시적으로 다루지 않았음
  - C17에서는 cast, lvalue conversion, function declarator에서 rvalue 한정자를 무시해야 한다는 점이 명확해짐
  - C17 § 6.7.6.3 ¶ 5에는 함수가 반환하는 타입이 `T`의 **unqualified version**이라고 명시되었고, 이 문구는 C17에서 추가됨
  - 반환 타입의 `const` 한정이 달라도 함수 타입 대입이 합법이 될 수 있음
  - 추가 논의는 [DR423](http://www.open-std.org/JTC1/sc22/wg14/www/docs/dr_423.htm)과 [DR481](http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2257.htm#dr_481)에 있음
- ## 불완전 구조체와 전역 변수
  - 전역 변수 선언 시점에 `struct foo`가 불완전 타입이라 크기를 알 수 없어도, 이후 같은 translation unit에서 타입이 완성되는 경우 특정 상황에서는 허용됨
  - 전역 변수나 불완전 타입 배열에도 비슷한 논리가 적용됨
  - 이 내용은 [DR016](http://www.open-std.org/jtc1/sc22/wg14/docs/rr/dr_016.html)에서도 다뤄짐
- ## `void` 타입의 external object
  - 내부 linkage를 가진 `void` 타입 변수 선언은 합법이 아니지만, external linkage를 가진 `void` 타입 변수 선언은 문법상 합법이고 C11 표준 어디에도 명시적으로 금지되어 있지 않음
  - C11 § 6.2.5 ¶ 19에 따르면 `void` 타입은 값의 빈 집합으로 구성된 **완성될 수 없는 불완전 객체 타입**임
  - C11 § 6.3.2.1 ¶ 1은 lvalue를 `void`가 아닌 객체 타입의 표현식으로 정의하므로, `void` 타입 객체 이름 `foo`는 유효한 lvalue가 아님
  - C11 기준으로 external `void` 객체에 대해 의미 있고 conforming한 연산은 떠올리기 어려움
  - [DR012](http://www.open-std.org/jtc1/sc22/wg14/www/docs/dr_012.html)는 타입을 `const void`로 바꾸면 객체 `foo`의 주소를 취하는 것이 합법이라고 다루며, 이는 의도된 기능보다는 oversight처럼 보임
- ## 포인터-`const` 변환
  - `T`가 파생 객체 타입일 때 `cp` 대입은 합법이지만, `cpp` 대입이 합법인지에는 짧은 답이 없음
  - 이 주제는 [implicit pointer to const conversion 관련 글](https://stefansf.de/post/implicit-pointer-to-const-conversion/)에서 다룸

### 배열, 문자열 리터럴, 포인터 조정
- ## 배열은 포인터가 아님
  - 배열 초기화와 포인터 초기화는 동등하지 않음
  - 첫 번째 형태는 자동 또는 정적 저장 기간의 **수정 가능한 배열**을 초기화함
  - 두 번째 형태는 정적 저장 기간을 가진 배열을 가리키는 포인터를 초기화하며, 그 배열은 반드시 수정 가능하지 않음
  - 배열은 포인터가 아니며, 자세한 내용은 [관련 글](https://stefansf.de/post/arrays-are-second-class-citizens/)에서 다룸
- ## `a`, `&a`, `&a[0]`
  - `int a[42];`에서 `a`, `&a`, `&a[0]`는 모두 배열의 첫 번째 원소 주소로 평가됨
  - 하지만 세 표현식의 **타입은 서로 다르므로** 상호 교환해 사용할 수 없음
  - 자세한 내용은 [관련 글](https://stefansf.de/post/arrays-are-second-class-citizens/)에서 다룸
- ## 배열 매개변수와 지역 배열
  - 함수 매개변수 타입이 “`T`의 배열”이면 “`T`를 가리키는 포인터”로 조정됨
  - 매개변수 `x`가 `int[42]`처럼 보여도 실제로는 `int *`로 취급됨
  - 지역 변수 `y`가 `int[42]`이면 `sizeof(y)`는 `42 * sizeof(int)`임
  - 일반적으로 객체 포인터 크기는 정수 42개의 크기와 같지 않으므로 `sizeof(x) == sizeof(y)`는 보통 `false`임
  - 자세한 내용은 [관련 글](https://stefansf.de/post/arrays-are-second-class-citizens/)에서 다룸

### 연산자, 평가 순서, 제어 흐름
- ## `x+++y`
  - C에서는 C++처럼 새 연산자를 정의할 수 없으므로 `+++` 같은 새 연산자는 없음
  - `x+++y`는 기존 연산자의 조합으로 해석되며 `(x++) + y`와 동등함
  - `--*--p`도 새 연산자가 아니라 기존 연산자의 조합임
  - `--*--p`는 `--(*(--p))`와 동등하며, 예시에서는 `-1`로 평가되고 부작용으로 `x[0]`에 `-1`을 대입함
- ## 산술 피연산자의 평가 순서
  - 연산자 우선순위는 잘 정의되어 있지만, 산술 피연산자의 **평가 순서**는 정의되어 있지 않음
  - `(x=1) + (x=2)`는 두 대입의 순서가 정의되지 않아 `x`의 최종값이 `1`인지 `2`인지 정해지지 않으므로 정의되지 않은 동작임
  - `-std=c11 -O2` 옵션에서 GCC 8.2.1은 예시 표현식을 `4`로, Clang 7.0.0은 `3`으로 평가함
- ## 논리 연산자의 평가 순서
  - 논리 연산자 `&&`와 `||`에서는 피연산자의 평가 순서도 잘 정의됨
  - C 표준 표현으로는 첫 번째 피연산자 평가와 두 번째 피연산자 평가 사이에 **sequence point**가 존재함
  - 예시에서는 먼저 `x=1`이 평가되어 `true`가 되고, 이어서 `x=2`가 평가되어 역시 `true`가 되므로 전체 표현식은 `true`가 됨
- ## `switch`의 자유로운 본문 구조
  - `switch` 문 본문은 임의의 statement가 될 수 있어, loop와 `if`가 섞인 구조도 합법일 수 있음
  - 제어 표현식이 항상 `false`인 `if` 문 안쪽의 `true` branch라도 `case` label이 있으면 해당 문장은 live가 되며 `printf("1");`은 dead code가 아님
  - `case 2`로 점프하면 loop의 clause-1과 제어 표현식이 실행되지 않을 수 있으므로, 변수 `i`는 미리 초기화되어 있어야 함
  - `case 1`에 `break`가 없어 fall through가 일어나더라도, `case 1`이 `if`의 `true` branch에 있고 `case 2`가 `false` branch에 있으면 `case 2`를 건너뛰고 `case 3`으로 계속될 수 있음
  - 세 번의 호출 `foo(0); foo(1); foo(2);` 뒤 콘솔 출력은 `02313223`이 됨
  - loop와 switch를 섞은 유명한 실제 예시는 [Duff's device](https://en.wikipedia.org/wiki/Duff's_device)임

### 임시 객체 수명과 C 표준 버전 차이
- 특정 코드 조각은 C11에서는 정의되지 않은 동작이지만, C99에서는 그렇지 않을 수 있음
- C11에서는 특정 객체의 수명이 줄어들어, 함수 호출이 반환한 객체가 오른쪽 항이 평가되는 동안까지만 살아 있음
- C99에서는 같은 객체가 enclosing block 끝까지 살아 있음
- 수명이 끝난 객체를 참조하면 C11 § 6.2.4 ¶ 2에 따라 **정의되지 않은 동작**임
- C99에서도 automatic storage duration 객체의 수명은 가장 가까운 enclosing block에 묶이므로, 해당 블록 밖에서 객체를 참조하면 정의되지 않은 동작임
- C11 § 6.2.4 ¶ 8은 구조체 또는 union 타입의 non-lvalue expression이 array member를 포함하면 automatic storage duration과 temporary lifetime을 가진 객체를 참조한다고 규정함
- 이 임시 객체의 수명은 표현식이 평가될 때 시작되고, 포함하는 full expression 또는 full declarator 평가가 끝날 때 종료됨
- temporary lifetime을 가진 객체를 수정하려는 시도는 정의되지 않은 동작임
- 해당 예시는 [N1285](http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1285.htm)에서 가져온 것이며, 추가 논의도 거기에 있음

## Comments



### Comment 58094

- Author: neo
- Created: 2026-05-23T09:15:52+09:00
- Points: 1

###### [Lobste.rs 의견들](https://lobste.rs/s/4vhivn/c_programming_language_quiz) 
- **문항 4**는 C23에서는 유효하지 않지만, 그전에는 유효했음  
  문항 10은 정답도 오답도 아니라서 객관식이라고 하기엔 좀 거슬림  
  문항 15는 특히 문항 13과 관련해서 기술적으로 틀렸고, 문항 20은 “명시되지 않음”이라 역시 어느 답도 아님  
  문항 30은 읽기에 따라 애매함  
  그래도 31개 중 27개를 맞혔고, 컴파일러 개발자라는 점이 조금은 도움이 됨

- 네 문제쯤 풀고 나니, **C가 단순해서 사이드 프로젝트에 써볼 만하다**는 남아 있던 감각이 사라짐
  - GCC나 `clang`에서는 `-std=`&lt;language-standard&gt; `-pedantic -Wall -Wextra`를 쓰고 경고가 나올 때마다 실제로 고치며, **포인터 캐스팅**과 포인터 조작을 최대한 피하면 큰 함정은 없을 것 같음  
    요즘 GCC/`clang` 경고는 꽤 좋고, &lt;language-standard&gt;는 c89, c99, c11, c23을 쓸 수 있음
  - C는 단순하지만 **정의되지 않은 동작**을 둘러싼 곡예는 단순하지 않음  
    tcc 같은, 이상한 최적화를 하지 않는 컴파일러를 쓰면 기묘한 놀라움은 덜 겪게 됨

- 그냥 “여기서 가장 말도 안 되는 동작이 뭐일까?” 기준으로 골라서 **32개 중 21개**를 맞힘  
  틀린 것 대부분은 그 말도 안 됨의 수준을 충분히 깊게 생각하지 않아서였음  
  C는 15년 넘게 전에 조금 건드려본 정도인데, 이런 퀴즈를 보니 다시 해보고 싶어지지는 않음
  - 참고로 ChatGPT는 각 답 뒤에 나오는 추가 설명을 보지 않은 상태에서 **32개 중 22개**를 맞혔음

- C23 기준으로는 **문항 4**의 답이 유효하지 않음

- 흥미롭게도 한동안 C를 쓰지 않았는데 **32개 중 27개**를 맞힘  
  이런 것 때문에 정적 검사기와 린터에 의존해 왔음

- **문항 1**부터 이미 찝찝했음  
  그 포인터들이 어디서 올 수 있는지는 고려하지 않았고, 거기서 말한 경우가 성립하려면 매우 특수한 조건이 필요함  
  대부분의 경우에는 포인터를 만들려고 하는 것 자체가 정의되지 않은 동작이지만, 그래도 공정하다고 볼 수는 있겠음  
  문항 3은 정말 놀라웠고, 또 하나의 C 함정이었음  
  애초에 C 정수 리터럴에 확정 타입이 있다는 게 굉장히 짜증남  
  정수 승격 규칙이 어느 정도는 맞춰주지만 오류의 근원이기도 함  
  현대 언어는 대부분, 혹은 모든 **암시적 숫자 캐스팅**을 금지하고, 가능하면 문맥에서 리터럴 타입을 추론하며, 불가능하면 명시적 캐스팅을 요구해야 함  
  문항 6 이후로는 테스트를 믿지 못해서 포기했음  
  처음에는 문항 5의 답이 사실상 문항 6을 틀리게 만들도록 설계돼 있었기 때문이었지만, 다시 보니 문항 6 자체가 틀린 듯함  
  해설은 함수 호출이 정의되지 않은 동작이라고 하지만, 문제는 함수 정의가 합법인지 물었고, 아마 합법이었을 가능성이 큼
  - 메모리에서 두 배열이 인접해 있고, 하나의 첫 원소와 다른 하나의 마지막 원소 바로 다음을 가리키면 그런 상황이 됨  
    그리고 그게 아주 드문 경우는 아닌 것 같음

- `switch()` 문제는 정말 좋았음  
  까다롭지만 머릿속에서 풀어내는 과정이 아주 재미있었음
