20 수식과 상수 — 값이 되는 것
먼저 알아야 할 것
돌아보기
7장과 8장에서 수를 기계에 담는 법(2의 보수, IEEE 754)을 배웠다. 그러면 소스 코드에 수를 적으면 — 이를테면 12라고 쓰면 — 그것은 언제 기계의 비트가 되는가?
답. 컴파일 때다. 소스의 12는 그냥 글자 두 개(9장의 표현으로 문자 1과 2)이고, 컴파일러가 그것을 읽어 2의 보수 비트 패턴으로 번역해 실행 파일에 심는다. 소스에 적힌 값 표기를 리터럴(literal)이라 부른다 — “글자 그대로”라는 뜻이다. 글자의 세계와 비트의 세계를 잇는 다리가 컴파일러라는 것 — 16장의 릴레이가 여기서도 일하고 있다.
이 장의 필요성과 맥락
이 장이 끝나면
이 장에서 답할 질문
- 그러면 소스에
0.1이라고 적으면, 기계에는 정확히 0.1이 담기는가? l소문자를 쓰지 말라는 말을 자주 듣는데?- 16진 부동소수점은 왜 지수부
p가 필수인가? 그리고 왜 쓰는가? - 나눗셈은 왜 미룬 것인가? 사칙연산에서 하나만 빠지니 오히려 어색한데.
printf("안녕")도 값이 되는가? 수식의 자리에 함수 부르기가 적혀 있는데.
20.1 리터럴 — 소스에 적는 값
리터럴은 이미 여러 번 만났다. 헬로 월드의 0이 정수 리터럴이고, "안녕, 세상!\n"은 문자열 리터럴이다 — 따옴표 안의 글자들이 “글자 그대로” 값이 된다. 정수 리터럴에 소수점을 붙이면(3.5) 8장의 부동소수점 값이 된다.
문. 그러면 소스에 0.1이라고 적으면, 기계에는 정확히 0.1이 담기는가?
답. 아니다 — 8장에서 배운 그대로다. 컴파일러는 0.1이라는 리터럴을 가장 가까운 이웃(3FB9 9999 9999 999A)으로 번역한다. 리터럴은 “값을 그대로 적는 표기”이지만, 그 값이 표현 가능한 눈금 위에 없으면 번역 단계에서 이미 근사가 일어난다. 소스에 적었다고 정확한 것이 아니다 — 8장의 교훈이 소스 코드의 세계에서도 그대로 유효하다.
20.2 상수를 적는 법 — 한눈에 보기
앞 절에서 리터럴을 만났으니, 이 자리에서 소스에 값을 적는 표기를 한 번에 모아 둔다. 표준은 이것을 어휘 요소 조항(§6.4.4, §6.4.5)에 흩어 두었고, 실무에서 헷갈리는 것은 거의 언제나 접두어·접미어·이스케이프 셋이다.
이 절은 참조용이다. 지금 전부 외울 필요는 없고, 「그런 표기가 있다」는 지도만 얻어 두었다가 필요할 때 돌아오면 된다. 시연도 그런 뜻으로 읽는다 — 출력을 훑어 보며 표기와 결과를 눈으로 잇는 정도면 충분하다.
examples/ch20/constants.c
/* 상수를 적는 방법들 — 표기가 값과 타입을 어떻게 정하는가. */
#include <inttypes.h>
#include <stdio.h>
#include <string.h>
#include <uchar.h>
int main(void)
{
puts("[integer constants - four bases and digit separators (C23)]");
printf(" 1234 = %d\n", 1234); /* 10진 */
printf(" 0755 = %d <- a leading 0 means octal\n", 0755);
printf(" 0xFF = %d\n", 0xFF);
printf(" 0b1010 = %d <- binary, new in C23\n", 0b1010);
printf(" 1'000'000 = %d <- digit separators, new in C23\n", 1'000'000);
printf(" 0b11'10'11'01 = %d\n", 0b11'10'11'01);
puts("\n[접미어가 타입을 정한다]");
printf(" sizeof 1 = %zu, sizeof 1L = %zu, sizeof 1LL = %zu\n",
sizeof 1, sizeof 1L, sizeof 1LL);
printf(" sizeof 1U = %zu, sizeof 1wb = %zu ← wb 는 C23 의 _BitInt\n",
sizeof 1U, sizeof 1wb);
puts("\n[문자 상수 — 접두어가 타입을 정한다]");
printf(" 'a' 크기 %zu, 값 %d ← C 에서 문자 상수는 int 다\n",
sizeof 'a', 'a');
printf(" u8'a' 크기 %zu (char8_t)\n", sizeof u8'a');
printf(" u'a' 크기 %zu (char16_t)\n", sizeof u'a');
printf(" U'a' 크기 %zu (char32_t)\n", sizeof U'a');
printf(" L'a' 크기 %zu (wchar_t)\n", sizeof L'a');
/* 'ab' 같은 다중 문자 상수는 값이 구현 정의라 -Wmultichar 가 경고한다.
여기서는 경고를 켠 채 두고, 값은 본문의 실측 표로만 보인다. */
puts("\n[이스케이프 — 8진과 16진]");
printf(" '\\101' = %d, '\\x41' = %d ← 둘 다 'A'\n", '\101', '\x41');
printf(" \"\\x41\" \"1\" = \"%s\" ← 16진은 가장 긴 열을 먹는다.\n",
"\x41" "1");
puts(" 그래서 \"\\x411\" 이 아니라 문자열을 쪼개 이어 붙인다.");
puts("\n[부동소수점 상수]");
printf(" 3.14 1e3=%g 1.=%g .5=%g\n", 1e3, 1., .5);
printf(" 0x1p-3 = %g ← 16진 부동소수점. 지수부 p 는 필수다\n", 0x1p-3);
printf(" sizeof 1.0 = %zu, 1.0f = %zu, 1.0L = %zu\n",
sizeof 1.0, sizeof 1.0f, sizeof 1.0L);
printf(" 0.1 == 0.1f ? %s ← 접미어가 다르면 값도 다르다\n",
(double)0.1f == 0.1 ? "예" : "아니오");
puts("\n[문자열 리터럴]");
printf(" sizeof \"abc\" = %zu ← NUL 이 한 칸 더 붙는다\n", sizeof "abc");
printf(" \"hello, \" \"world\" = \"%s\" ← 인접한 것은 하나로 이어진다\n",
"hello, " "world");
printf(" \"a\\0b\": strlen = %zu, sizeof = %zu ← 안에 NUL 을 넣어도 배열은 남는다\n",
strlen("a\0b"), sizeof "a\0b");
printf(" sizeof u8\"가\" = %zu, sizeof u\"가\" = %zu, sizeof U\"가\" = %zu\n",
sizeof u8"\uAC00", sizeof u"\uAC00", sizeof U"\uAC00");
return 0;
}
실행 결과
[integer constants - four bases and digit separators (C23)]
1234 = 1234
0755 = 493 <- a leading 0 means octal
0xFF = 255
0b1010 = 10 <- binary, new in C23
1'000'000 = 1000000 <- digit separators, new in C23
0b11'10'11'01 = 237
[접미어가 타입을 정한다]
sizeof 1 = 4, sizeof 1L = 8, sizeof 1LL = 8
sizeof 1U = 4, sizeof 1wb = 1 ← wb 는 C23 의 _BitInt
[문자 상수 — 접두어가 타입을 정한다]
'a' 크기 4, 값 97 ← C 에서 문자 상수는 int 다
u8'a' 크기 1 (char8_t)
u'a' 크기 2 (char16_t)
U'a' 크기 4 (char32_t)
L'a' 크기 4 (wchar_t)
[이스케이프 — 8진과 16진]
'\101' = 65, '\x41' = 65 ← 둘 다 'A'
"\x41" "1" = "A1" ← 16진은 가장 긴 열을 먹는다.
그래서 "\x411" 이 아니라 문자열을 쪼개 이어 붙인다.
[부동소수점 상수]
3.14 1e3=1000 1.=1 .5=0.5
0x1p-3 = 0.125 ← 16진 부동소수점. 지수부 p 는 필수다
sizeof 1.0 = 8, 1.0f = 4, 1.0L = 16
0.1 == 0.1f ? 아니오 ← 접미어가 다르면 값도 다르다
[문자열 리터럴]
sizeof "abc" = 4 ← NUL 이 한 칸 더 붙는다
"hello, " "world" = "hello, world" ← 인접한 것은 하나로 이어진다
"a\0b": strlen = 1, sizeof = 4 ← 안에 NUL 을 넣어도 배열은 남는다
sizeof u8"가" = 4, sizeof u"가" = 4, sizeof U"가" = 8
20.3 정수 상수 (§6.4.4.1)
| 진법 | 표기 | 예 | 비고 |
|---|---|---|---|
| 10진 | 1 ~ 9 로 시작 | 1234 | |
| 8진 | 0 으로 시작 | 0755 = 493 | 가장 흔한 함정 — 08 은 오류다 |
| 16진 | 0x 또는 0X | 0xFF = 255 | |
| 2진 | 0b 또는 0B | 0b1010 = 10 | C23 에서 추가 |
표 20.1
뒤에 붙는 글자 — 접미어 — 도 있다. 10L, 1U, 3ULL 처럼 쓰는데, 이것은 값이 아니라 타입을 바꾸는 표시다.
| 접미어 | 무슨 표시인가 |
|---|---|
u U | 부호 없는 타입으로 |
l L | long 이상으로 |
ll LL | long long 이상으로 |
wb WB | _BitInt 로 (C23) |
표 20.2
지금은 「접미어가 타입을 바꾼다」는 사실만 알아 두면 된다. 여기 나온 타입 이름들과, 접미어가 없을 때 컴파일러가 어떤 타입을 고르는지의 정확한 규칙은 27장에서 정수를 정식으로 배운 뒤 「정수 상수의 타입」 절에서 다룬다. 미리 한 가지만 — 같은 값을 십진으로 적느냐 16진으로 적느냐에 따라 타입이 달라질 수 있다.
문. l 소문자를 쓰지 말라는 말을 자주 듣는데?
답. 글꼴에 따라 1(숫자 1)과 l(엘)이 거의 같아 보이기 때문이다. 10l 이 101 로 읽히는 사고가 실제로 있었다. 접미어는 대문자로 — 10L, 1UL — 가 오랜 관행이고, 이 책도 그렇게 쓴다. 접두어 0x 는 소문자가 관행이라 방향이 반대인데, 이유는 같다: 눈에 잘 갈리는 쪽을 고른 것이다.
20.4 C23 의 자리 구분자 '
긴 수를 읽기 좋게 끊는 표기가 C23 에 들어왔다. 값에는 아무 영향이 없다 — 표준의 말대로 상수의 값을 정할 때 무시된다.
표준이 직접 든 예가 함정까지 함께 보여 준다. 실측으로 확인하면 이렇다.
| 적은 것 | 결과 | 왜 |
|---|---|---|
12'34 | 1234 | 자리 구분자는 숫자 사이에만 |
0b11'10'11'01 | 237 | 2진에도 쓸 수 있다 |
0x1'2'3'4AB'C'D | 305441741 | 16진에도 |
0x'FF | 오류 — “digit separator after base indicator” | 0x 바로 뒤에는 못 온다 |
'1'2 | 오류 | 문자 상수 '1' 뒤에 2 가 온 것으로 읽힌다 |
표 20.3
마지막 줄이 이 표기의 유일한 위험이다. 구분자는 숫자와 숫자 사이에서만 구분자이고, 맨 앞에 오면 작은따옴표 — 즉 문자 상수의 시작 — 으로 읽힌다.
20.5 문자 상수 (§6.4.4.4)
타입 이름들은 9장에서 문자 집합을, 27장에서 정수를 배운 뒤라야 온전히 읽힌다 — 지금은 접두어가 타입을 정한다는 것만 보면 된다.
| 표기 | 타입 | 실측 크기 | 비고 |
|---|---|---|---|
'a' | int | 4 | ★C 에서 문자 상수는 char 가 아니다 |
u8'a' | char8_t | 1 | C23. UTF-8 코드 단위 하나여야 한다 |
u'a' | char16_t | 2 | UTF-16 코드 단위 하나 |
U'a' | char32_t | 4 | UTF-32 코드 단위 하나 |
L'a' | wchar_t | 4 (리눅스), 2 (윈도) | 와이드 리터럴 인코딩(9장) |
'ab' | int | 4 | 값이 구현 정의. GCC 는 -Wmultichar 로 경고한다 |
표 20.4
흔한 오해. “'a' 는 char 니까 크기가 1이다”
C++ 에서는 맞고 C 에서는 틀리다. 실측하면 갈린다 — 같은 sizeof('a') 가 C 에서 4, C++ 에서 1이다.
표준의 문장은 「정수 문자 상수는 int 타입을 가진다」(§6.4.4.4p11)이다. 값은 「그 문자를 char 객체에 담았다가 int 로 변환했을 때의 값」이라, 값은 우리가 기대하는 그대로이고 타입만 넓다.
이 차이가 드러나는 자리는 대개 sizeof 와 _Generic, 그리고 함수 오버로딩이 있는 C++ 쪽이다. C 만 쓴다면 실질적 피해는 거의 없지만, 두 언어에 함께 쓰이는 헤더에서는 알고 있어야 한다.
이스케이프는 다음이 전부다(§6.4.4.4).
| 갈래 | 표기 | 주의 |
|---|---|---|
| 반드시 이스케이프 | \' \\ | 작은따옴표와 역슬래시는 반드시 이 형태로 |
| 해도 되고 안 해도 되고 | \" \? | 문자열 안에서는 \" 가 필요하다 |
| 비그래픽 문자 | \a \b \f \n \r \t \v | 의미는 §5.2.3 에 정의되어 있다 |
| 8진 | \ + 8진 숫자 | 최대 세 자리 |
| 16진 | \x + 16진 숫자 | ★자릿수 제한이 없다 |
| 유니버설 문자 이름 | \uXXXX \UXXXXXXXX | 기본 문자 집합 밖의 글자를 이름으로 |
표 20.5
반례. "\x411" — 16진 이스케이프가 뒷글자까지 먹는다
표준이 못박는다 — 8진이나 16진 이스케이프는 그것을 이룰 수 있는 가장 긴 문자 열이다(§6.4.4.4p7). 8진은 세 자리에서 멈추지만 16진은 멈추지 않는다.
"\x411" /* 'A'(0x41) 뒤에 '1' 이 아니라, 0x411 을 요구한 것 */실측하면 GCC 가 hex escape sequence out of range 라고 경고한다. 고치는 법은 문자열을 쪼개어 잇는 것이다 — 인접한 문자열 리터럴은 하나로 이어지므로 (아래), "\x41" "1" 은 정확히 "A1" 이 된다.
20.6 부동소수점 상수 (§6.4.4.3)
십진 형태는 소수점이 있거나 지수부가 있거나 둘 중 하나를 갖춰야 한다. 그래서 1., .5, 1e3 이 모두 유효하고 1 은 정수 상수다.
부동소수점에도 접미어가 있다(타입의 정체는 8장과 50장에서 본다).
| 접미어 | 타입 | 실측 크기 |
|---|---|---|
| (없음) | double | 8 |
f F | float | 4 |
l L | long double | 16 (x86-64 리눅스) |
df dd dl | _Decimal32 / _Decimal64 / _Decimal128 (C23) | 4 / 8 / 16 |
표 20.6
16진 부동소수점(C99)도 있다 — 0x1p-3 은 정확히 0.125 다.
문. 16진 부동소수점은 왜 지수부 p 가 필수인가? 그리고 왜 쓰는가?
답. e 를 쓸 수 없기 때문이다. 16진에서 e 는 숫자 14라 지수의 시작으로 읽을 수 없다. 그래서 이진 지수를 뜻하는 p 를 따로 두었고, p 는 생략할 수 없다 — 생략하면 어디까지가 가수인지 정할 수 없다. 0x1p-3 은 「1 × 2−3」이다.
쓰는 이유는 정확성이다. 8장에서 본 대로 십진 0.1 은 이진에 정확히 담기지 않지만, 16진 표기는 이진 표현을 그대로 옮겨 적는 것이라 번역에서 반올림이 일어나지 않는다. 그래서 부동소수점 시험의 기대값, 표준 라이브러리의 상수 표, 부동소수점을 다루는 논문의 예제가 이 표기를 쓴다. printf 의 %a 가 같은 표기로 찍어 준다 (부록 B).
접미어가 값까지 바꾼다는 것도 짚어 둔다. 실측에서 (double)0.1f == 0.1 은 거짓이다 — 0.1f 는 float 눈금 위의 가장 가까운 값이고, 0.1 은 double 눈금 위의 가장 가까운 값이라 서로 다른 수다(8·50장).
20.7 문자열 리터럴 (§6.4.5)
| 표기 | 원소 타입 | 인코딩 | 실측 sizeof |
|---|---|---|---|
"가" | char | 리터럴 인코딩(9장) | 4 (UTF-8 3바이트 + NUL) |
u8"가" | char8_t | 언제나 UTF-8 | 4 |
u"가" | char16_t | UTF-16 | 4 (코드 단위 1 + NUL) |
U"가" | char32_t | UTF-32 | 8 |
L"가" | wchar_t | 와이드 리터럴 인코딩 | 8 (리눅스) |
표 20.7
네 가지 성질을 함께 기억한다.
- 끝에 NUL 이 붙는다.
sizeof "abc"는 3이 아니라 4다. - 인접한 것은 하나로 이어진다(번역 단계 6).
"hello, " "world"는 한 문자열이다. 긴 문자열을 줄로 나눠 적는 정석이고, 위의\x함정을 푸는 방법이기도 하다. 단 접두어가 섞이면 안 된다 —u"a" U"b"는 컴파일 오류다(제약 위반). - 안에 NUL 을 넣어도 배열은 잘리지 않는다.
"a\0b"는strlen이 1,sizeof는 4다 — 문자열 함수가 멈출 뿐 데이터는 다 있다. - 수정하면 정의되지 않은 동작이다. 실측하면 대개 실행 중에 죽는다(읽기 전용 구역에 놓이기 때문이다). 그래서 문자열 리터럴은
const char *로 받는다.
반례. char *s = "abc"; s[0] = 'X';
컴파일은 통과하고(C 에서 문자열 리터럴의 타입은 const 가 아닌 char[N] 이다), 실행하면 죽는다 — 실측에서 SIGSEGV 였다.
C++ 는 이 자리를 아예 막았다(문자열 리터럴이 const char[N] 이라 대입이 오류다). C 는 옛 코드 호환 때문에 열어 두었고, 그래서 습관으로 막아야 한다 — const char *s = "abc"; 로 받으면 컴파일 시점에 잡힌다. -Wwrite-strings 로 경고를 켜 두는 방법도 있다.
20.8 상수처럼 보이는 것들
| 표기 | 정체 | 자세히 |
|---|---|---|
RED (열거 상수) | 정수 상수다. 타입은 int | 55장 — 보통 식별자 마당에 산다 |
nullptr | nullptr_t 타입의 예약어 (C23) | 36장 |
true false | bool 값을 주는 예약어 (C23) | 30장 |
(int[]){1,2,3} 복합 리터럴 | 상수가 아니라 객체다 — 주소를 얻을 수 있다 | 47장 |
#define N 100 | 상수가 아니라 토큰 치환이다 | 57장 |
constexpr int n = 10; | C23 의 진짜 상수 — 상수 수식에 쓸 수 있다 | 23장 |
표 20.8
마지막 두 줄의 구별이 실무에서 값을 한다. 매크로는 타입도 스코프도 없고(57장), const int 는 C 에서 상수 수식이 아니다 — C++ 와 갈리는 자리다. 다만 「못 쓴다」가 아니라 어디서 못 쓰는가가 정확한 말이라, 실측으로 갈라 둔다.
const int n = 10; 을 이렇게 쓰면 | 결과(GCC, C23) |
|---|---|
블록 안에서 int a[n]; | 통과한다 — 다만 상수 배열이 아니라 가변 길이 배열(VLA)이 된다 |
파일 스코프에서 int a[n]; | 오류 — variably modified 'a' at file scope |
블록 안에서 static int a[n]; | 오류 — storage size of 'a' isn't constant |
case n: | 오류 — case label does not reduce to an integer constant |
표 20.9
즉 진짜 상수 수식이 필요한 자리에서 걸린다. C23 의 constexpr 이 그 자리를 메우려고 들어왔다.
20.9 수식 — 계산되어 값이 되는 것
리터럴을 연산자로 엮으면 수식(expression)이 된다. 2 + 3 * 4 처럼. 수식의 정의는 한 문장이면 된다 — 계산되어(평가되어) 값이 되는 것. 이 “값이 된다”는 성질이 수식의 전부이자, 앞으로 C를 읽는 눈의 절반이다. 코드의 어느 자리에 값이 필요하든, 그 자리에는 수식이 올 수 있다 — 리터럴 하나도 가장 단순한 수식이다.
이 장의 연산자는 넷뿐이다 — 더하기 +, 빼기 -, 곱하기 *, 그리고 괄호 ( ). 이 책의 나선형 원칙대로, 나머지 연산자들은 필요해지는 장에서 하나씩 합류한다(비교는 30장, 나눗셈은 28장 — 나눗셈이 뒤로 미뤄진 이유는 잠시 뒤에).
시연이다. 아래 예제의 %d는 “이 자리에 정수 값을 십진법으로 찍어라” 라는 표시로, 정식 설명은 22장에서 한다 — 지금은 계산 결과를 눈으로 확인하는 창으로만 쓴다.
examples/ch20/expr.c
#include <stdio.h>
int main(void)
{
printf("%d\n", 2 + 3 * 4); /* 곱셈이 먼저 계산된다 */
printf("%d\n", (2 + 3) * 4); /* 괄호가 순서를 명시한다 */
return 0;
}
실행 결과
14
20
20.10 순서 — 우선순위, 그리고 괄호라는 관행
위 시연의 첫 줄이 14인 것은 곱셈이 덧셈보다 먼저 계산됐기 때문이다. 수학 시간의 관례 그대로이고, C는 이런 “누가 먼저인가”의 서열 — 우선순위(precedence) — 을 모든 연산자에 정해 두었다.
여기서 이 책의 권고를 미리 밝힌다. 우선순위 표를 외우지 말고, 괄호를 써라. C의 연산자는 수십 개이고 서열표는 15줄이 넘는다 — 그것을 다 외운 사람들끼리도 헷갈려서 사고가 난다. 둘째 줄처럼 괄호로 순서를 명시하면 외울 것도, 오해할 것도 없다. 전체 서열표는 부록에 참고 자료로 두고, 본문은 “산술은 수학 관례대로, 그 밖에는 괄호”로 간다.
문. 나눗셈은 왜 미룬 것인가? 사칙연산에서 하나만 빠지니 오히려 어색한데.
답. 정수의 나눗셈이 수학의 나눗셈과 다르기 때문이다. C에서 7 / 2는 3.5가 아니라 3이다 — 정수끼리의 나눗셈은 소수점 아래를 버린 몫이고, 짝꿍 연산자 %(나머지)와 세트로 이해해야 뜻이 선다. 그 “버림”의 정확한 규칙(음수에서는 어느 쪽으로 버리나)까지 포함해서, 7장의 정수 세계 위에서 제대로 다룰 가치가 있는 주제라 28장에 자리를 잡아 두었다. 어중간하게 지금 소개해서 “7/2=3.5인 줄 알았는데”라는 오개념을 만드는 것보다, 정색하고 한 번에 다루는 것이 낫다는 판단이다.
문. printf("안녕")도 값이 되는가? 수식의 자리에 함수 부르기가 적혀 있는데.
답. 좋은 눈이다 — 된다. 함수를 부르는 것 자체가 수식이고, 따라서 값이 된다. 그 값이 무엇이고 어디로 가는지가 바로 다음 장의 주제다. 이 질문 하나로 다음 장의 문이 이미 반쯤 열렸다.
20.11 미리 심는 주의 하나 — 계산의 시간 순서는 다를 수 있다
우선순위에 관해, 지금은 씨앗만 심어 둘 주의가 하나 있다. 우선순위는 “누가 누구의 재료인가”라는 묶기의 규칙이지, 기계가 시간상 무엇을 먼저 계산하는가가 아니다 — 이를테면 덧셈의 왼쪽 재료와 오른쪽 재료 중 어느 쪽을 먼저 계산할지는 표준이 정해 두지 않았고, 컴파일러마다 다를 수 있다. 2 + 3 같은 수식에서야 아무래도 좋지만, 재료의 계산이 출력 같은 흔적을 남기는 경우에는 그 흔적의 순서가 달라질 수 있다는 뜻이다.
다만 안심할 것 — 문장과 문장 사이의 순서는 철석같이 보장된다. 19장의 “위에서 아래로 한 문장씩”은 계약이다. 흔들릴 수 있는 것은 한 문장 안쪽뿐이고, 그 안쪽의 정확한 규칙(부수효과, 시퀀스 포인트라는 개념)은 재료가 더 갖춰진 뒤 33장에서 정면으로 다룬다. 지금의 실전 수칙은 하나면 된다: 순서가 중요한 일들은 한 문장에 욱여넣지 말고 문장을 나눈다.
정리하면 — 소스에 값을 적는 표기가 리터럴이고, 계산되어 값이 되는 것이 수식이며, 묶는 순서는 산술 관례와 괄호로 다스리되 한 문장 안의 계산 시간 순서는 달라질 수 있다. 다음 장에서는 값을 소비하고 생산하는 가장 중요한 장치 — 함수를 부르는 법 — 를 익힌다. 헬로 월드의 심장 printf(...)가 드디어 정면으로 다뤄진다.