21 Expressions and constants — the things that become values
What to know first
Looking back
Chapters 6–7 taught how to hold numbers in the machine (two’s complement, IEEE 754). So when a number is written in source code — when you write 12, say — when does it become the machine’s bits?
A. At compile time. The 12 in the source is just two characters (in the terms of chapter 8, the characters 1 and 2), and the compiler reads it, translates it into a two’s-complement bit pattern and plants it in the executable. A notation for a value written in source is called a literal — meaning “as the letters say.” That the bridge between the world of letters and the world of bits is the compiler — chapter 17′s relay is at work here too.
The need for this chapter, and its context
By the end of this chapter
The questions this chapter answers
- Then if I write
0.1in the source, does the machine hold exactly 0.1? - Why the advice never to use a lowercase
l? - Why must a hex float have the
pexponent, and why use one at all? - So should every
#defineconstant become aconstexpr? - Is it the same as C++‘s
constexpr? - Why was division put off? Leaving one of the four operations out feels odd.
- Is
printf("hello")a value too? A function call is written in the position of an expression.
21.1 Literals — values written in source#
We have met literals several times already. The 0 of hello world is an integer literal, and "Hello, world!\n" is a string literal — the characters inside the quotation marks become a value “as they are.” Add a decimal point to an integer literal (3.5) and it becomes a floating-point value from chapter 7.
Q. Then if I write 0.1 in the source, does the machine hold exactly 0.1?
A. It does not — exactly as chapter 7 taught. The compiler translates the literal 0.1 into its nearest neighbour (3FB9 9999 9999 999A). A literal is “a notation writing the value as it is”, but if that value is not on the grid of representable numbers, the approximation has already happened at translation. Writing it in the source does not make it exact — chapter 7′s lesson holds in the world of source code too.
21.2 Writing constants — all of them at a glance#
Having met literals in the previous section, this is the place to gather every notation for writing a value in source at once. The standard scatters them through its lexical clauses (§6.4.4, §6.4.5), and what trips people in practice is nearly always one of three things: a prefix, a suffix or an escape.
This section is for reference. There is no need to memorise it now — take away the sense that these notations exist, and come back when you need them. Read the listing in that spirit too: skimming the output and matching notation to result is enough.
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;
}
Output
[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
21.3 Integer constants (§6.4.4.1)#
| Base | Notation | Example | Note |
|---|---|---|---|
| decimal | starts with 1~9 | 1234 | |
| octal | starts with 0 | 0755 = 493 | the commonest trap — 08 is an error |
| hexadecimal | 0x or 0X | 0xFF = 255 | |
| binary | 0b or 0B | 0b1010 = 10 | added in C23 |
Table 21.1 — The bases an integer constant can be written in
Letters can follow too — a suffix, as in 10L, 1U, 3ULL. It marks not the value but the type.
| Suffix | What it marks |
|---|---|
u U | an unsigned type |
l L | long or wider |
ll LL | long long or wider |
wb WB | _BitInt (C23) |
Table 21.2 — The suffixes of an integer constant
For now, that a suffix changes the type is all you need. The type names above, and the exact rule for which type the compiler picks when there is no suffix, come in chapter 28 once integers have been met properly, in “The type of an integer constant”. One thing in advance — writing the same value in decimal or in hexadecimal can give it a different type.
Q. Why the advice never to use a lowercase l?
A. Because in many fonts 1 (one) and l (ell) look almost identical. 10l being read as 101 has really happened. Suffixes in capitals — 10L, 1UL — is the long-standing convention, and this book follows it. The 0x prefix is conventionally lowercase, which looks like the opposite rule, but the reason is the same: whichever is easier to tell apart.
21.4 The C23 digit separator '#
A notation for breaking up long numbers arrived in C23. It has no effect on the value — in the standard’s words it is ignored when determining the value of the constant.
The standard’s own example shows the traps along with the feature. Measured:
| Written | Result | Why |
|---|---|---|
12'34 | 1234 | a separator goes only between digits |
0b11'10'11'01 | 237 | binary takes it too |
0x1'2'3'4AB'C'D | 305441741 | so does hexadecimal |
0x'FF | error — “digit separator after base indicator” | it may not follow 0x |
'1'2 | error | read as the character constant '1' followed by 2 |
Table 21.3 — Where the C23 digit separator works and where it does not
The last row is this notation’s one danger. A separator is a separator only between two digits; at the front it is read as a single quote — the start of a character constant.
21.5 Character constants (§6.4.4.5)#
The type names read fully only after chapter 8′s character sets and chapter 28′s integers — for now just see that the prefix settles the type.
| Notation | Type | Measured size | Note |
|---|---|---|---|
'a' | int | 4 | ★in C a character constant is not a char |
u8'a' | char8_t | 1 | C23. Must be one UTF-8 code unit |
u'a' | char16_t | 2 | one UTF-16 code unit |
U'a' | char32_t | 4 | one UTF-32 code unit |
L'a' | wchar_t | 4 (Linux), 2 (Windows) | the wide literal encoding (chapter 8) |
'ab' | int | 4 | value implementation-defined. GCC warns with -Wmultichar |
Table 21.4 — Character constant prefixes and their types
A common misconception. “'a' is a char, so its size is 1”
True in C++ and false in C. Measured, the very same sizeof('a') is 4 in C and 1 in C++.
The standard’s sentence is “an integer character constant has type int” (§6.4.4.5p11). Its value is “what results when an object of type char holding that character is converted to int”, so the value is what you expect and only the type is wider.
Where it shows is mostly sizeof, _Generic, and overloading on the C++ side. Write only C and the practical harm is near zero, but in a header used from both languages it must be known.
The escapes are exactly these (§6.4.4.5).
| Kind | Notation | Note |
|---|---|---|
| must be escaped | \' \\ | the single quote and the backslash must take this form |
| optional | \" \? | inside a string \" is needed |
| non-graphic characters | \a \b \f \n \r \t \v | their meanings are defined in §5.2.3 |
| octal | \ + octal digits | at most three digits |
| hexadecimal | \x + hex digits | ★there is no digit limit |
| universal character names | \uXXXX \UXXXXXXXX | naming characters outside the basic set |
Table 21.5 — The kinds of escape sequence
Counter-example. "\x411" — a hex escape eats the letter after it
The standard nails it: each octal or hexadecimal escape sequence is the longest sequence of characters that can constitute the escape sequence (§6.4.4.5p7). Octal stops at three digits; hexadecimal does not stop.
"\x411" /* not 'A'(0x41) then '1', but a request for 0x411 */Measured, GCC warns hex escape sequence out of range. The fix is to split the string and let it join — adjacent string literals concatenate (below), so "\x41" "1" is exactly "A1".
21.6 Floating constants (§6.4.4.3)#
The decimal form must have either a decimal point or an exponent part. So 1., .5 and 1e3 are all valid, while 1 is an integer constant.
Floating constants take suffixes too (what the types are comes in chapters 7 and 52).
| Suffix | Type | Measured size |
|---|---|---|
| (none) | double | 8 |
f F | float | 4 |
l L | long double | 16 (x86-64 Linux) |
df dd dl | _Decimal32 / _Decimal64 / _Decimal128 (C23) | 4 / 8 / 16 |
Table 21.6 — The suffixes of a floating constant
There are also hexadecimal floating constants (C99) — 0x1p-3 is exactly 0.125.
Q. Why must a hex float have the p exponent, and why use one at all?
A. Because e is unavailable: in hexadecimal e is the digit 14 and cannot start an exponent. So p, meaning a binary exponent, was given its own place, and p cannot be omitted — without it there is no telling where the significand ends. 0x1p-3 is “1 × 2−3”.
The reason to use one is exactness. As chapter 7 showed, decimal 0.1 does not sit exactly in binary, whereas the hex notation transcribes the binary representation itself, so no rounding happens in translation. Hence its use in floating-point tests’ expected values, in the standard library’s tables of constants, and in papers about floating point. printf’s %a prints in the same notation (appendix B).
Worth noting too that the suffix changes the value. Measured, (double)0.1f == 0.1 is false — 0.1f is the nearest value on the float grid and 0.1 the nearest on the double grid, and those are different numbers (chapters 7 and 52).
21.7 String literals (§6.4.5)#
| Notation | Element type | Encoding | Measured sizeof |
|---|---|---|---|
"가" | char | the literal encoding (chapter 8) | 4 (3 UTF-8 bytes + NUL) |
u8"가" | char8_t | always UTF-8 | 4 |
u"가" | char16_t | UTF-16 | 4 (1 code unit + NUL) |
U"가" | char32_t | UTF-32 | 8 |
L"가" | wchar_t | the wide literal encoding | 8 (Linux) |
Table 21.7 — String literal prefixes and their encodings
Four properties go together.
- A NUL is appended.
sizeof "abc"is not 3 but 4. - Adjacent literals join into one (translation phase 6).
"hello, " "world"is one string. It is the standard way to split a long string across lines, and the way out of the\xtrap above. But the prefixes must not be mixed —u"a" U"b"is a compile error (a constraint violation). - A NUL inside does not cut the array short.
"a\0b"hasstrlen1 andsizeof4 — the string functions stop, the data is all there. - Modifying one is undefined behaviour. Measured, it usually dies at run time (it is placed in a read-only section). So take string literals as
const char *.
Counter-example. char *s = "abc"; s[0] = 'X';
It compiles (in C the type of a string literal is char[N], not const), and it dies when run — SIGSEGV in the measurement.
C++ closed this off entirely (a string literal is const char[N] there, so the assignment is an error). C left it open for compatibility with old code, so the habit has to close it — take it as const char *s = "abc"; and the compiler catches it. Turning on -Wwrite-strings is another way.
21.8 Things that look like constants#
| Notation | What it really is | More |
|---|---|---|
RED (an enumeration constant) | an integer constant, of type int | chapter 59 — it lives in the ordinary-identifier yard |
nullptr | a keyword of type nullptr_t (C23) | chapter 37 |
true false | keywords yielding bool values (C23) | chapter 31 |
(int[]){1,2,3} a compound literal | not a constant but an object — you can take its address | chapter 48 |
#define N 100 | not a constant but token replacement | chapter 61 |
constexpr int n = 10; | C23′s real constant — usable in a constant expression | the very next section |
Table 21.8 — What the things that look like constants really are
The last two rows pay off in practice. A macro has neither type nor scope (chapter 61), and a const int is not a constant expression in C — the place where C and C++ part. But “cannot be used” is less accurate than where it cannot be, so here it is, measured.
Given const int n = 10;, writing | Result (GCC, C23) |
|---|---|
int a[n]; inside a block | accepted — but as a variable length array, not a constant one |
int a[n]; at file scope | error — variably modified 'a' at file scope |
static int a[n]; inside a block | error — storage size of 'a' isn't constant |
case n: | error — case label does not reduce to an integer constant |
Table 21.9 — Where a const int is refused
That is, it fails wherever a real constant expression is required. C23′s constexpr came in to fill that place — which is the next section.
21.9 constexpr — the real constant C23 brought in#
For a long time C had four things worth calling a “constant”, and all four fell short somewhere.
| What | Has a type? | A constant expression? | What is missing |
|---|---|---|---|
100 (a literal) | yes | yes | no name — the same value gets written everywhere |
#define N 100 | no | (judged after replacement) | no scope, no type, no name in the debugger (chapter 61) |
enum { N = 100 } | int only | yes | cannot hold long long, double or a string |
const int n = 100; | yes | no | fails where a real constant is needed — array sizes, case labels |
Table 21.10 — Four kinds of ‘constant’ and what each lacks
constexpr is the word that came in to fill the blanks in that table — a value with a type, a name and a scope, which is also a constant expression.
examples-en/ch20/constexpr.c
/* C23's constexpr - how far a "real constant" goes. */
#include <stdio.h>
constexpr int table_size = 4; /* file scope: static storage duration, internal linkage */
constexpr double half = 0.5;
constexpr long long big = 1LL << 40;
int table[table_size]; /* an array size - and not a variable length array */
static_assert(sizeof table / sizeof table[0] == 4, "table_size is a constant");
constexpr int doubled = table_size * 2; /* a constant built from a constant */
struct limits { int low, high; };
constexpr struct limits range = { 1, 9 };
static_assert(range.high == 9, "a member of a constexpr struct is a constant");
/* the preprocessor knows nothing of constexpr - the program prints that fact */
#if table_size == 4
static const char *preproc = "the preprocessor saw table_size == 4";
#else
static const char *preproc = "the preprocessor never saw it: the name became 0";
#endif
static const char *classify(int x)
{
switch (x) {
case table_size: return "exactly the table size"; /* a case label */
case doubled: return "twice the table size";
default: return "something else";
}
}
struct packed {
unsigned flags : table_size; /* the width of a bit-field */
};
int main(void)
{
static int copy = table_size + 1; /* static initialization */
enum { same_again = table_size }; /* the value of an enumeration constant */
printf("[constexpr is a constant expression]\n");
printf(" array size : %zu\n", sizeof table / sizeof table[0]);
printf(" case label : %s\n", classify(4));
printf(" case label : %s\n", classify(8));
printf(" static init : %d\n", copy);
printf(" enum value : %d\n", (int)same_again);
printf(" bit-field : %d bits\n", table_size);
printf(" struct member : range.high = %d (checked with static_assert)\n",
range.high);
printf("\n[it has a type, unlike a macro]\n");
printf(" half = %g (double)\n", half);
printf(" big = %lld (long long)\n", big);
printf(" const is implicit: %s\n",
_Generic(&table_size, const int *: "yes, &table_size is const int *",
int *: "no", default: "?"));
printf("\n[a block-scope constexpr is an ordinary object with an address]\n");
constexpr int local = 7;
const int *p = &local;
printf(" local = %d, read through a pointer = %d\n", local, *p);
printf("\n[but the preprocessor runs before any of this]\n");
printf(" %s\n", preproc);
printf(" #if and #define live in a different world (chapter 57)\n");
/* every line below is a compile error - the value must be exactly representable.
constexpr unsigned int m = -1; the value does not fit
constexpr float f = 0.1; 0.1 as a double is not exact as a float
constexpr int *q = © a pointer initializer must be null
constexpr const char *s = "abc"; the same reason
constexpr volatile int v = 1; volatile, restrict and atomic are banned
constexpr int no_init; it must be a definition with an initializer */
return 0;
}
Output
[constexpr is a constant expression]
array size : 4
case label : exactly the table size
case label : twice the table size
static init : 5
enum value : 4
bit-field : 4 bits
struct member : range.high = 9 (checked with static_assert)
[it has a type, unlike a macro]
half = 0.5 (double)
big = 1099511627776 (long long)
const is implicit: yes, &table_size is const int *
[a block-scope constexpr is an ordinary object with an address]
local = 7, read through a pointer = 7
[but the preprocessor runs before any of this]
the preprocessor never saw it: the name became 0
#if and #define live in a different world (chapter 57)
21.9.1 What it is you are writing#
constexpr goes where a storage-class specifier goes (the seven words of that slot are gathered in chapter 45). Its meaning is one sentence of the standard — “an object declared with a storage-class specifier constexpr has its value permanently fixed at translation time; if not yet present, a const-qualification is implicitly added to the object’s type. The declared identifier is considered a constant expression.”1
So it works in every place the demonstration showed.
| Where it may be used | With const int instead |
|---|---|
an array size, int table[table_size]; | becomes a variable length array, or an error |
case table_size: | error |
static int copy = table_size + 1; | error |
enum { same = table_size }; | error |
static_assert(...) | error |
the width of a bit-field, unsigned f : table_size; | error |
the initializer of another constexpr | error |
Table 21.11 — Where constexpr may be used
Three things to remember about the grammar. 1. It must be a definition, with an initializer. constexpr int n; is an error (“constexpr requires an initialized data declaration”). 2. The initializer must be a constant expression. 3. The only storage-class specifiers it may be combined with are auto, register and static, so extern constexpr and thread_local constexpr are errors.
21.9.2 How it differs from const#
A common misconception. “constexpr is a stronger const”
They look alike, but the two words answer different questions.
constis a promise: “I will not change it through this name.” It says nothing about when the value is decided — it may be decided at run time.constexpris a fact: “this value is decided at translation time.” As a consequenceconstfollows (the standard adds it implicitly).
The direction is reversed, in other words. So const int n = f(); is fine while constexpr int n = f(); is not — a function call is not a constant expression. The demonstration’s _Generic makes that implicit const visible: the type of &table_size is const int *.
The working test is one line — if the value will be used as an array size or a case label, constexpr; if it only means “I will not change it”, const.
21.9.3 The value has to fit exactly#
constexpr carries one constraint that no other declaration does: the value of the initializer shall be exactly representable in the target type, with no change of value.2 A value that an ordinary initialization would have quietly truncated is caught here.
| Written | gcc 14 | clang 22 |
|---|---|---|
constexpr unsigned int m = -1; | error — “constexpr initializer not representable in type of object” | error — “not exactly representable” |
constexpr unsigned int m = -1U; | OK | OK |
constexpr float f = 0.1; | error — 0.1 as a double is not on the float grid | error |
constexpr float f = (float)0.1; | OK — say what you mean with a cast | OK |
constexpr signed short s = 70000; | warning (-Woverflow) | error |
constexpr unsigned char c = 300; | warning (-Woverflow) | error |
Table 21.12 — What the two compilers do when the value does not fit exactly
The last two rows are what the measurement bought. A constraint violation requires a diagnostic but does not dictate its shape, so gcc’s warning does not break the rules. Still, unless warnings are raised to errors (-Werror), a quietly truncated value becomes a constant — which is where the habit of building with -Wall -Wextra pays (chapter 16).
Pointers carry a stronger constraint — they must be null.
constexpr int *p = nullptr; /* OK */
constexpr const char *s = "abc"; /* error: "constexpr pointer initializer is not null" */
constexpr char s2[] = "abc"; /* OK — take it as an array */Trying to name a string constant with constexpr is the wall everybody meets on day one. The answer is to declare an array instead of a pointer.
21.9.4 What happens at file scope#
constexpr is often put where a macro used to be, so whether it may go in a header becomes a practical question straight away. The standard’s answer: a constexpr object at file scope has static storage duration, and its identifier has internal linkage (like static). Every translation unit that sees the same textual definition gets its own object, so a header definition does not collide (chapter 56).
Inside a block it is an ordinary local object — automatic storage duration, and its address can be taken. The demonstration’s &local confirms it. It is a “constant” and yet an object, which is where it parts from a macro.
Q. So should every #define constant become a constexpr?
A. For naming a value, yes. It gains a type and a scope, it shows up in the debugger under its name, and the compiler catches mistakes.
But one job stays with the macro — the preprocessor’s own conditionals. The preprocessor runs before the compiler and knows nothing of a constexpr name, so inside #if that name becomes 0. The demonstration prints the fact: #if table_size == 4 is judged not true but false (0 == 4). No error, no warning — it just quietly goes that way.
So the rule splits like this — values you branch on with #if stay #define; constants used as values in code become constexpr. Why the preprocessor’s world is separate at all is chapter 61′s subject.
Q. Is it the same as C++‘s constexpr?
A. The name is the same, the reach is not. C++‘s constexpr also applies to functions, running them at compile time, but C23 has no constexpr functions — it applies to object definitions only. constexpr int f(void) { ... } really is an error (“constexpr can only be used in variable declarations”, clang).
There is a reason C brought it in that narrowly. Compile-time execution is a feature that doubles the size of a language, which would have to be traded against C’s character as a small, predictable language. The place where a value is needed right now is filled by this section’s constexpr, and nothing beyond it was added.
21.10 Expressions — the things that are evaluated into values#
Weave literals together with operators and you have an expression, as in 2 + 3 * 4. The definition takes one sentence — that which is calculated (evaluated) into a value. This property of “becoming a value” is the whole of an expression, and half of the eye for reading C from here on. Wherever a value is needed in code, an expression may go there — a single literal is the simplest expression of all.
This chapter has only four operators — plus +, minus -, times *, and parentheses ( ). Following this book’s spiral principle, the remaining operators join in the chapters where they become necessary (comparison in chapter 31, division in chapter 29 — why division was put off, in a moment).
Here is the demonstration. The %d in the example below is a mark meaning “print an integer value here in decimal”; its formal explanation is in chapter 23 — for now it is only a window for seeing results.
examples-en/ch20/expr.c
#include <stdio.h>
int main(void)
{
printf("%d\n", 2 + 3 * 4); /* the multiplication is computed first */
printf("%d\n", (2 + 3) * 4); /* the parentheses state the order */
return 0;
}
Output
14
20
21.11 Order — precedence, and the practice of parentheses#
The first line of the demonstration is 14 because the multiplication was calculated before the addition. Exactly the convention of the mathematics lesson, and C has fixed such a ranking of “who goes first” — precedence — for every operator.
Here we state this book’s recommendation in advance. Do not memorise the precedence table; use parentheses. C has dozens of operators and the ranking table runs to more than fifteen rows — even people who have memorised it all get confused and cause accidents. Make the order explicit with parentheses, as in the second line, and there is nothing to memorise and nothing to misread. The full table is in the appendix as reference material; the text goes with “arithmetic by the mathematical convention, parentheses everywhere else.”
Q. Why was division put off? Leaving one of the four operations out feels odd.
A. Because integer division differs from division in mathematics. In C 7 / 2 is not 3.5 but 3 — division between integers is the quotient with the fractional part discarded, and it only makes sense understood as a set with its partner operator % (remainder). Including the exact rule of that discarding (which way it goes for negative numbers), it is a subject worth treating properly on top of chapter 6′s world of integers, so it has been given a place in chapter 29. Better to treat it squarely, all at once, than to introduce it half-heartedly now and create the misconception “I thought 7/2 was 3.5.”
Q. Is printf("hello") a value too? A function call is written in the position of an expression.
A. Good eye — it is. Calling a function is itself an expression, and therefore becomes a value. What that value is and where it goes is exactly the next chapter’s subject. That one question has already half-opened the next chapter’s door.
21.12 One caution planted early — the order in time may differ#
About precedence there is one caution to plant as a seed. Precedence is a rule of binding — who is whose material — not of what the machine calculates first in time. Which of the left and right materials of an addition is calculated first, for instance, is not fixed by the standard and may differ between compilers. In an expression like 2 + 3 it does not matter at all, but when calculating a material leaves a trace such as output, the order of those traces may differ.
Be reassured on one point, though — the order between statements is guaranteed absolutely. Chapter 20′s “one statement at a time, top to bottom” is a contract. What can waver is only the inside of a single statement, and the exact rules there (side effects, the notion of sequence points) are faced head on in chapter 34, once more material is in place. For now one practical rule suffices: do not cram order-sensitive work into one statement; split the statements.
To summarise — a notation writing a value in source is a literal, that which is evaluated into a value is an expression, and the order of binding is governed by arithmetic convention and parentheses, while the order in time inside one statement may differ. In the next chapter we learn the most important device that consumes and produces values — how to call a function. Hello world’s heart, printf(...), is finally treated head on.