Chapter 5: Basic values and data
The chapter introduces the distinction between C values and their representations, and presents the abstract state machine as a platform-independent way to reason about program state.
After this chapter you will be able to
- Values versus representations. Explain why portable C code should reason primarily about values rather than how a particular computer represents them.
- Observable states and results. Explain which results of a computation can be observed and identify what constitutes observable state in this section.
- Optimization and abstract state machine. Explain why a compiler can remove or rearrange computations while still realizing the program's abstract state machine.
- Abstract values across platforms. Explain that a C value is independent of a particular program run or representation, and illustrate this with the consistent effects of 0 across C platforms.
- Number-like nature of values. Explain the section's claim that every value in a C program is a number or translates to a number, including values used for text or truth.
- Types of C values. Explain that every C value has a statically determined type, and that its type determines which operations are possible and their results.
- Binary representation and results. Explain why operand values and types alone do not always determine an operation’s result across C platforms, and how a type’s binary representation supplies the platform-specific information needed.
- Abstract representation and execution. Distinguish a type’s binary representation from its object representation, and explain what determines transitions in the program’s abstract state machine.
- Observable behavior and optimization. Explain that a compiler need not follow the program’s operations exactly if its changes preserve the observable states of the abstract state machine. Describe why observing or using an intermediate value can prevent a change.
- Type-dependent optimization limits. Explain how the type of a value can determine whether combining additions is allowed: unsigned overflow remains consistent with the separate operations, while a possible signed-integer or floating-point overflow may prevent the change because it could terminate the program.
- Basic type organization. Describe the two levels of basic type specification and the four classes of basic numbers.
- Narrow integer promotions. Explain what happens to narrow integer types before arithmetic, including the roles of char and bool.
- Integer ranks and ranges. Explain which relationships among integer ranges C constrains and why exact ranges remain platform-dependent.
- Purpose-based integer choices. Apply the section’s guidance for choosing size_t, unsigned, signed, and ptrdiff_t according to the quantities being represented.
- Literal forms and meanings. Describe the integer, floating-point, character, and string literal forms introduced here, including what their prefixes or exponent markers mean.
- Integer literal type selection. Explain why equally valued integer literals can have different types, and why decimal spelling is preferred when expressing negative values.
- Floating literal values and types. Explain why a decimal floating-point literal's effective value may differ from its written value, and state how floating-point suffixes determine type.
- CMPLX value construction. Describe how CMPLX combines real and imaginary floating-point parts into a complex value, and distinguish its float, double, and long double forms.
- Imaginary unit expressions. Explain how the reserved macro I represents the imaginary unit and can be used with ordinary floating-point literals to specify complex values.
- Unary operator result types. Explain how an operand’s type affects unary + and -, including why -1U is a large positive unsigned value rather than a negative integer.
- Conversion into narrower types. Distinguish conversions that fit the destination type from the section’s problematic signed and unsigned narrowing examples, and explain the advice to avoid narrowing conversions.
- Mixed signedness comparisons. Explain how converting mixed signed and unsigned operands can make comparisons surprising or platform-dependent, and state the section’s advice for choosing arithmetic types.
- Initialized object values. Explain why variables should normally be initialized, even when an initial value might seem unnecessary.
- Scalar and array initializers. Distinguish scalar initializers from array initializers, and describe how an array initializer determines elements and, when necessary, array length. Explain why designated initializers are preferred for aggregate data.
- Names for meaningful constants. Explain why a value used for a particular purpose should have a name, and why equal values with different purposes should be distinguished.
- Const-qualified object guarantees. Distinguish a const-qualified object from a C constant and explain what the qualifier does and does not guarantee.
- Positional enumeration values. Explain how enumeration constants receive positional or explicit values and how their fixed ordering can remove the need to track an array’s order manually.
- Integer constant expression limits. Explain what makes an expression eligible to define an enumeration constant, including the distinction between const-qualified and constexpr objects.
- C23 underlying type choices. Describe how C23 handles enumeration constants that do not fit in signed int and why explicitly specifying an underlying integer type can be preferable.
- Macro Replacement and Definition. Explain that a #define macro replaces an identifier in subsequent program code with specified text, allowing names for values such as double constants. Describe the parts of the definition shown here.
- Compound literal objects and constants. Explain that a compound literal consists of a parenthesized type followed by an initializer and defines an object. Describe why using one in a macro can resemble a named constant without making it a constant suitable for ICE as introduced here.
- Constexpr constant expressions. Explain why constexpr is useful when a named constant, including one of a complicated type, must be usable as a file-scope initializer. State that C23 permits constexpr on declarations and compound literals.
- Exact-fit initializer rule. Explain that a constexpr initializer is checked where it is declared and that a conversion changing its value is a compiler error. Describe the example in which converting a value with digits after the decimal point to unsigned loses those digits.
- Binary and object representations. Distinguish a type’s binary representation, which models its possible values, from an in-memory object representation, which describes how values are stored. State that the same value may have different binary representations.
- Unsigned range and arithmetic. Explain that an unsigned integer type has values from 0 through N−1 and that its arithmetic operates modulo N. State how to find the maximum value for the unsigned types named here.
- Binary precision and bit significance. Describe how indexed binary bits represent a nonnegative integer, what precision and width mean for an unsigned type, and why p bits give a maximum of 2^p−1.
- Unsigned values as bit sets. Explain how an unsigned value can represent a subset of bit positions rather than just a number.
- Bitwise set operations. Describe the set operations performed by |, &, ^, and ~, including why complement differs from the three binary operations.
- Bit sets for flags. Explain how one-bit constants and bitwise operations represent and check settings such as membership in a flock.
- Left shift and powers. Explain that left-shifting an unsigned value by a count moves its set bits to higher positions and corresponds numerically to multiplying by that power of two. State that left shift is used to specify powers of two.
- Shift precision limits. Explain that bits shifted beyond the type’s precision are omitted and that the right-hand shift count must be less than the precision.
- Right shift interpretation. Describe right shift as moving bits toward less-significant positions, corresponding to integer division by a power of two, with bits shifted off omitted. Identify >>= and <<= as the corresponding assignment operators.
- Boolean Values and Assignment. Explain why bool is considered unsigned and how assignment to it differs from assignment to other unsigned types.
- Signed integer representation. Explain how a signed integer represents positive and negative values in C23, including its relationship to the corresponding unsigned type.
- Signed overflow consequences. Distinguish unsigned wraparound from undefined signed overflow, and explain why the compiler may treat a signed counting loop as infinite.
- Fixed width type guarantees. Explain why a program might choose a fixed-width integer type instead of a standard integer type, and state what the signed and unsigned fixed-width names guarantee when provided.
- Bit-precise type widths. Explain why C23 provides _BitInt types and state the supported width range.
- Bit-precise literal types. Describe how wb or WB integer-literal suffixes specify bit-precise types, including how signedness and the minimum sufficient width are determined.
- Bit-precise arithmetic width. Explain the result width used when bit-precise integers are combined and how that can cause an unsigned result to wrap.
- Floating-point range and precision. Explain why floating-point types cannot represent every mathematical real number: their range is limited, and expansions may have to be cut off. Distinguish DBL_MIN from the most negative double value.
- Binary floating-point value model. Describe the section’s simplified formula for a floating-point value using a sign, exponent, and mantissa bits, and explain what precision counts. State why values in this model are fractions with a power of two in the denominator.
- Floating-point arithmetic consequences. Explain how cutting off values during intermediate computations can affect results, especially when operands differ greatly in magnitude. Describe why the section warns against equality comparisons and says results may only be judged close.
What it builds on
- Operands and operators (chapter 4) · Assumes familiarity with evaluating operands and operators.
- Evaluation order (chapter 4) · Assumes prior discussion of expression evaluation order.
- Boolean context (chapter 4) · The use of a value in a control expression.
- Arithmetic (chapter 4) · Arithmetic expressions using operands.
- Comparison (chapter 4) · The use of comparison operators.
- Definitions (chapter 2) · The role of an initializer in an object definition.
- Operators that modify objects (chapter 4) · Operators that modify an object.
- Logic (chapter 4) · Logical operators.
Section by section
Introduction
The chapter introduces the distinction between C values and their representations, and presents the abstract state machine as a platform-independent way to reason about program state.
- Values versus representations: Explain why portable C code should reason primarily about values rather than how a particular computer represents them.
Why it matters: This distinction helps you reason about C code without assuming your computer’s representation is universal. - Abstract program state: Describe the limited picture of C’s abstract state machine given here: it offers a mostly platform-independent view of execution, with objects that have a fixed type and a value that can change over time.
Why it matters: This gives you a way to discuss what a program does without starting from one machine’s representation.
Key terms: values, representations, portable code, abstract state machine, objects, type, value
5.1 The abstract state machine
The section uses a small C computation to introduce observable program state and explain why compilers may optimize away steps that do not affect it.
- Values during program execution: Trace how evaluating expressions produces intermediate values and how an assignment changes a variable's value in the example.
Why it matters: Following how values change makes it easier to distinguish a program's steps from its observable results. - Observable states and results: Explain which results of a computation can be observed and identify what constitutes observable state in this section.
Why it matters: Observability determines which effects an implementation must preserve. - Optimization and abstract state machine: Explain why a compiler can remove or rearrange computations while still realizing the program's abstract state machine.
Why it matters: It explains why the executable need not perform every operation written in the source code.
Key terms: values, variables, expressions, observable states, addressable memory, output device, optimization, abstract state machine
5.1.1 Values
The section presents C values as abstract, number-like entities and relates program data and execution state to the goal of platform-independent results.
- Abstract values across platforms: Explain that a C value is independent of a particular program run or representation, and illustrate this with the consistent effects of 0 across C platforms.
Why it matters: It lets you reason about what a value means without tying that meaning to one machine or run. - Number-like nature of values: Explain the section's claim that every value in a C program is a number or translates to a number, including values used for text or truth.
Why it matters: It explains why the section discusses values beyond ordinary arithmetic as number-like entities. - Execution data and state: Describe what the section counts as execution data and what determines execution state; explain why it seeks results that depend on the program specification rather than a platform-specific executable.
Why it matters: It distinguishes repeatability for one executable from the stronger aim of portable results.
Key terms: value, representation, control expression, values, numbers, truth values, data, state, executable, outside intervention
5.1.2 Types
This subsection establishes that every C value has a statically determined type that governs its operations and their results.
- Types of C values: Explain that every C value has a statically determined type, and that its type determines which operations are possible and their results.
Why it matters: Type is central to understanding what an expression can do and what result it produces.
Key terms: type, value, statically determined
5.1.3 Binary representation and the abstract state machine
The section explains how platform-specific binary representations help determine operation results, how programs can observe those representations, and how these facts define C’s abstract state machine.
- Binary representation and results: Explain why operand values and types alone do not always determine an operation’s result across C platforms, and how a type’s binary representation supplies the platform-specific information needed.
Why it matters: It explains why the same C type can depend on characteristics of the platform running the program. - Observable type characteristics: Describe how a C program can obtain information needed to determine a type’s binary representation.
Why it matters: A program can discover relevant platform characteristics rather than simply assume them. - Abstract representation and execution: Distinguish a type’s binary representation from its object representation, and explain what determines transitions in the program’s abstract state machine.
Why it matters: It separates the model that defines a program’s computation from the details of physical storage.
Key terms: binary representation, operands, type, platform, observable, C library headers, SIZE_MAX, SIZE_WIDTH, object representation, abstract state machine
5.1.4 Optimization
This section explains why a compiler may change how a C program executes while preserving observable behavior, and why types can limit those optimizations.
- Observable behavior and optimization: Explain that a compiler need not follow the program’s operations exactly if its changes preserve the observable states of the abstract state machine. Describe why observing or using an intermediate value can prevent a change.
Why it matters: It explains how compiled code can differ from the written operations without changing what the program observably does. - Type-dependent optimization limits: Explain how the type of a value can determine whether combining additions is allowed: unsigned overflow remains consistent with the separate operations, while a possible signed-integer or floating-point overflow may prevent the change because it could terminate the program.
Why it matters: The same apparent arithmetic simplification may be permitted for one type but not another.
Key terms: abstract state machine, observable states, optimization, overflow, unsigned type, signed integer types, floating-point types
5.2 Basic types
The section classifies C’s basic numeric types, explains narrow-integer promotion and platform-dependent ranges, and gives purpose-based guidance for choosing types.
- Basic type organization: Describe the two levels of basic type specification and the four classes of basic numbers.
Why it matters: This organization helps you distinguish a type’s numeric class from the purpose expressed by a header-provided type. - Narrow integer promotions: Explain what happens to narrow integer types before arithmetic, including the roles of char and bool.
Why it matters: A narrow type’s declared signedness does not by itself tell you the type used in an arithmetic expression. - Integer ranks and ranges: Explain which relationships among integer ranges C constrains and why exact ranges remain platform-dependent.
Why it matters: Rank gives useful range relationships without promising that a type has the same exact width on every platform. - Purpose-based integer choices: Apply the section’s guidance for choosing size_t, unsigned, signed, and ptrdiff_t according to the quantities being represented.
Why it matters: Matching the type to what a quantity means reduces reliance on platform-specific integer ranges. - Fractional and complex choices: State which types the section recommends for fractional and complex calculations.
Why it matters: The kind of number in a calculation guides the choice between integer, real floating-point, and complex types. - Special-purpose arithmetic types: Describe the purposes of the specialized preprocessor and time types mentioned in the section.
Why it matters: These types name specialized uses rather than promising a universal precision or time unit.
Key terms: basic types, unsigned integers, signed integers, real floating-point numbers, complex floating-point numbers, precision, narrow types, promoted, signed int, char
5.3 Specifying values
This section explains how C literals specify values and how their spelling affects their type and, for floating-point literals, their effective value.
- Literal forms and meanings: Describe the integer, floating-point, character, and string literal forms introduced here, including what their prefixes or exponent markers mean.
Why it matters: Recognizing a literal's form lets you read the value the source code specifies. - String concatenation and negation: Explain how consecutive string literals form text and why a leading minus sign is not part of a numerical literal.
Why it matters: The distinction between a literal and an operator matters when working out an expression's type. - Integer literal type selection: Explain why equally valued integer literals can have different types, and why decimal spelling is preferred when expressing negative values.
Why it matters: The base used to spell an integer can change the type of the resulting expression. - Integer suffix type controls: Describe how integer suffixes alter type selection, using U, L, LL, and the C23 wb suffix as examples.
Why it matters: A suffix can change a literal's type without changing the value it denotes. - Floating literal values and types: Explain why a decimal floating-point literal's effective value may differ from its written value, and state how floating-point suffixes determine type.
Why it matters: The digits written in source code do not always identify the exact stored floating-point value.
Key terms: decimal integer literal, octal integer literal, hexadecimal integer literal, binary integer literal, decimal floating-point literal, hexadecimal floating-point literal, string literals, numerical literals, negation operator, first fit rule
5.3.1 Complex constants
This section explains how to specify complex values in C using standard macros and why nonstandard imaginary-suffix literals are not portable.
- Complex type availability: Explain that complex types are not supported on every C platform, how to check for support, and which header supplies full support.
Why it matters: Complex-value code must account for platform support and the header it needs. - CMPLX value construction: Describe how CMPLX combines real and imaginary floating-point parts into a complex value, and distinguish its float, double, and long double forms.
Why it matters: These macros provide a standard way to form a complex value from its two parts. - Imaginary unit expressions: Explain how the reserved macro I represents the imaginary unit and can be used with ordinary floating-point literals to specify complex values.
Why it matters: I lets you write complex values in a form close to mathematical notation. - Imaginary suffix portability: Distinguish the widely supported imaginary-suffix literal form from a form that portable C code can rely on.
Why it matters: A notation that works with many compilers is not necessarily portable C.
Key terms: __STDC_NO_COMPLEX__, <complex.h>, <tgmath.h>, CMPLX, CMPLXF, CMPLXL, real part, imaginary part, I, imaginary unit
5.4 Implicit conversions
This section shows how implicit conversions affect C values and expression results, and why arithmetic types should be chosen to make those conversions harmless.
- Unary operator result types: Explain how an operand’s type affects unary + and -, including why -1U is a large positive unsigned value rather than a negative integer.
Why it matters: The minus sign alone does not tell you whether a C expression has a negative value. - Conversion into narrower types: Distinguish conversions that fit the destination type from the section’s problematic signed and unsigned narrowing examples, and explain the advice to avoid narrowing conversions.
Why it matters: An assignment can compile while producing a platform-dependent or unintended value. - Mixed floating arithmetic precision: Explain why mixing an integer with a floating-point operand can be harmless for small values but lose distinctions for values such as INT_MAX.
Why it matters: A mixed-type expression may lose a numerical distinction even when its arithmetic looks simple. - Mixed signedness comparisons: Explain how converting mixed signed and unsigned operands can make comparisons surprising or platform-dependent, and state the section’s advice for choosing arithmetic types.
Why it matters: The same comparison can defy an intuitive reading, and some outcomes depend on the platform’s type widths.
Key terms: unary -, unary +, promoted operand, unsigned int, implicit conversions, implementation defined, narrowing conversions, modulus, INT_MAX, float
5.5 Initializers
This section explains why objects should be initialized and how scalar, array, designated, and empty initializers work.
- Initialized object values: Explain why variables should normally be initialized, even when an initial value might seem unnecessary.
Why it matters: A known starting value makes an object's state predictable without usually imposing a lasting performance cost. - Scalar and array initializers: Distinguish scalar initializers from array initializers, and describe how an array initializer determines elements and, when necessary, array length. Explain why designated initializers are preferred for aggregate data.
Why it matters: The initializer's form affects which array elements receive values and can even establish the array's length. - Empty default initializer: State how the empty-brace default initializer is used and what changed in C23.
Why it matters: Empty braces provide a general default when you do not know a type's specific initialization syntax.
Key terms: initializer, defined state, abstract machine, scalar types, array initializers, incomplete type, designated initializers, aggregate data types, default initializer, { }
5.6 Named constants
This section explains why programs should name meaningful constants and keep constants with different meanings distinct, even when their values happen to match.
- Names for meaningful constants: Explain why a value used for a particular purpose should have a name, and why equal values with different purposes should be distinguished.
Why it matters: Clear, distinct names make changes easier to maintain and show what each value means.
Key terms: named constants, constants with different meanings
5.6.1 Read-only objects
This section distinguishes C constants from read-only objects and explains the different guarantees of const-qualified objects, string literals, and constexpr objects.
- Const-qualified object guarantees: Distinguish a const-qualified object from a C constant and explain what the qualifier does and does not guarantee.
Why it matters: Read-only access does not necessarily mean a value stays the same over time. - String literal read-only status: Explain why string literals must be treated as read-only even though their type does not protect them from modification, and describe how the bird array relates to its literals.
Why it matters: The type of a string literal alone does not tell you that it must be treated as read-only. - Constexpr value stability: Distinguish the guarantee given for a C23 constexpr object from the guarantee given for an object that is merely const-qualified.
Why it matters: The distinction prevents you from treating every read-only object as a compile-time value.
Key terms: constant, const-qualified objects, read-only, string literals, char const[], bird, constexpr, const-qualified, compile time
5.6.2 Enumerations
This section explains how enumeration constants name integer values, when they qualify as integer constant expressions, and how C23 determines or explicitly specifies an enumeration’s underlying type.
- Positional enumeration values: Explain how enumeration constants receive positional or explicit values and how their fixed ordering can remove the need to track an array’s order manually.
Why it matters: The declared order can provide useful named values without manually assigning each position. - Small enumeration constant types: Distinguish the usefulness of small enumeration constants from declaring variables of the enumeration type, and state the type of the constants when they all fit in signed int.
Why it matters: It prevents confusing a named enumeration constant with a variable of its enumeration type. - Integer constant expression limits: Explain what makes an expression eligible to define an enumeration constant, including the distinction between const-qualified and constexpr objects.
Why it matters: A value that looks constant in source code is not necessarily valid for defining an enumeration constant. - C23 underlying type choices: Describe how C23 handles enumeration constants that do not fit in signed int and why explicitly specifying an underlying integer type can be preferable.
Why it matters: An explicit underlying type makes the intended range and signedness clearer when large values are involved.
Key terms: enumeration constants, positional value, explicit value, enumeration type, signed int, tag, integer constant expression, ICE, const-qualified, constexpr
5.6.3 Macros
This section explains how #define macros provide textual replacements for constant values and gives a naming convention for them.
- Macro Replacement and Definition: Explain that a #define macro replaces an identifier in subsequent program code with specified text, allowing names for values such as double constants. Describe the parts of the definition shown here.
Why it matters: It distinguishes a macro's textual substitution from declaring a typed constant. - Macro Naming Convention: State the book's recommended naming convention for macros and why the names are made visually distinctive.
Why it matters: Distinctive names help a reader recognize macros in code.
Key terms: macro, textual replacement, preprocessor, #define, replacement text, macro names, all caps
5.6.4 Compound literals
This section explains how compound literals can be used in macros to provide values of a chosen type, while warning that they define objects rather than true constants and require care in macro definitions.
- Compound literal objects and constants: Explain that a compound literal consists of a parenthesized type followed by an initializer and defines an object. Describe why using one in a macro can resemble a named constant without making it a constant suitable for ICE as introduced here.
Why it matters: The distinction prevents you from treating a macro’s compound literal as a constant in every context. - Compound literal macro precautions: Describe the section’s precautions for putting a compound literal in a macro replacement: use a const-qualified type for the named-constant purpose, separate the macro name from the opening parenthesis, and do not include a terminating semicolon. State how continued definition lines are marked and aligned.
Why it matters: These precautions keep the macro definition from changing meaning or inserting unwanted text where it is used.
Key terms: compound literal, object, ICE, initializer, const-qualified, macro replacement, function-like macro, continuation markers
5.6.5 The constexpr construct
This section introduces C23’s constexpr for named constant expressions, explains its exact-fit initializer rule, and illustrates its use with compound literals.
- Constexpr constant expressions: Explain why constexpr is useful when a named constant, including one of a complicated type, must be usable as a file-scope initializer. State that C23 permits constexpr on declarations and compound literals.
Why it matters: This explains when constexpr helps where the preceding named-constant techniques are less useful. - Exact-fit initializer rule: Explain that a constexpr initializer is checked where it is declared and that a conversion changing its value is a compiler error. Describe the example in which converting a value with digits after the decimal point to unsigned loses those digits.
Why it matters: The declaration catches a value-losing conversion rather than silently accepting it. - Constexpr compound literal arrays: Describe the section’s constexpr compound literal as an array of character arrays whose remaining positions are zero-filled. Explain that const qualification is implied and that the compiler may use this knowledge to replace a direct indexed lookup with the corresponding string literal or reuse that literal.
Why it matters: The example shows how constexpr can give the compiler opportunities to use less space or improve execution.
Key terms: constexpr, constant expression, file scope, compound literals, initializer, conversion, unsigned, compound literal, const qualification, CORVID_NAMES
5.7 Binary representations
Distinguishes a type’s binary representation from the in-memory representation of its objects and notes that one value can have multiple binary representations.
- Binary and object representations: Distinguish a type’s binary representation, which models its possible values, from an in-memory object representation, which describes how values are stored. State that the same value may have different binary representations.
Why it matters: This distinction keeps a model of values from being mistaken for how an object is stored.
Key terms: binary representation, in-memory object representation, value, type
5.7.1 Unsigned integers
This section explains how the precision and binary representation of unsigned integers determine their range and arithmetic.
- Unsigned range and arithmetic: Explain that an unsigned integer type has values from 0 through N−1 and that its arithmetic operates modulo N. State how to find the maximum value for the unsigned types named here.
Why it matters: This connects an unsigned type’s range to the arithmetic its values obey. - Binary precision and bit significance: Describe how indexed binary bits represent a nonnegative integer, what precision and width mean for an unsigned type, and why p bits give a maximum of 2^p−1.
Why it matters: Precision links the bits used to represent a value with the type’s maximum value and unsigned arithmetic.
Key terms: unsigned integer types, ring, integers modulo, _MAX, UINT_MAX, ULONG_MAX, bits, precision, width, least-significant bit
5.7.2 Bit sets and bitwise operators
The section explains how unsigned values can represent sets of bits, how bitwise operators manipulate those sets, and how programs use them for flags.
- Unsigned values as bit sets: Explain how an unsigned value can represent a subset of bit positions rather than just a number.
Why it matters: This interpretation makes the later set operations and flag examples meaningful. - Bitwise set operations: Describe the set operations performed by |, &, ^, and ~, including why complement differs from the three binary operations.
Why it matters: The set meanings let you reason about bitwise results without treating them as arithmetic. - Bit sets for flags: Explain how one-bit constants and bitwise operations represent and check settings such as membership in a flock.
Why it matters: Flags are a practical use of set membership in an unsigned value. - Bitwise versus logical operators: Distinguish bitwise & and | from logical && and ||, despite their similar spellings.
Why it matters: Similar-looking operators express different operations, so confusing them changes what a program does. - C23 bit operation facilities: Describe examples of the bit-set queries provided by C23's <stdbit.h> and the availability caveat.
Why it matters: These facilities can answer common bit-set questions, provided the platform has the new header.
Key terms: bit set, unsigned value, base set, precision, union, intersection, symmetric difference, complement, flags, power of 2
5.7.3 Shift operators
This section explains how shift operators move bits in unsigned values, how those shifts relate to powers of two, and why shift counts must be limited.
- Left shift and powers: Explain that left-shifting an unsigned value by a count moves its set bits to higher positions and corresponds numerically to multiplying by that power of two. State that left shift is used to specify powers of two.
Why it matters: You can read a left shift both as moving set bits and as scaling a number by a power of two. - Shift precision limits: Explain that bits shifted beyond the type’s precision are omitted and that the right-hand shift count must be less than the precision.
Why it matters: Precision tells you when a shifted bit disappears and whether a shift count is permitted. - Right shift interpretation: Describe right shift as moving bits toward less-significant positions, corresponding to integer division by a power of two, with bits shifted off omitted. Identify >>= and <<= as the corresponding assignment operators.
Why it matters: You can interpret a right shift as both a movement of bits and an integer calculation.
Key terms: left-shift operator, <<, powers of 2, bit sets, precision, second operand, shift operation, right-shift operation, >>, less-significant bits
5.7.4 Boolean values
This section explains C’s Boolean values, their special assignment behavior, and when to use bool or include <stdbool.h>.
- Boolean Values and Assignment: Explain why bool is considered unsigned and how assignment to it differs from assignment to other unsigned types.
Why it matters: Calling bool unsigned does not mean its assignments behave like ordinary unsigned integer assignments. - Purpose of Boolean Variables: Describe the reason the section gives for using a bool variable.
Why it matters: The section gives a specific reason to choose bool: keeping an assigned value Boolean. - Older C Boolean Compatibility: State when <stdbool.h> should still be included for Boolean names.
Why it matters: Including the header keeps code using Boolean names compatible with older C systems.
Key terms: bool, false, true, unsigned type, modulus rule, bool variables, C23, <stdbool.h>
5.7.5 Signed integers
Explains how signed integers are represented in C and why signed overflow makes arithmetic, negation, and bit operations hazardous.
- Signed integer representation: Explain how a signed integer represents positive and negative values in C23, including its relationship to the corresponding unsigned type.
Why it matters: This representation explains both the range of signed values and why their bit patterns resemble unsigned ones. - Signed overflow consequences: Distinguish unsigned wraparound from undefined signed overflow, and explain why the compiler may treat a signed counting loop as infinite.
Why it matters: A loop that appears to rely on a signed counter wrapping can behave differently after compiler optimization. - Minimum value negation: Explain why negating the minimum signed integer can overflow.
Why it matters: Even ordinary-looking negation is not safe for every signed integer. - Unsigned bit operation choice: Justify the section’s recommendation to use unsigned types for bit operations.
Why it matters: Choosing unsigned types avoids the negative-signed-value shift issues flagged here.
Key terms: signed types, unsigned types, sign bit, twos’ complement, signed overflow, undefined behavior, as-if rule, abstract state machine, INT_MIN, INT_MAX
5.7.6 Fixed-width integer types
This section explains when fixed-width integer types are useful, what their names guarantee, and how to use their associated macros and C23 format specifiers.
- Fixed width type guarantees: Explain why a program might choose a fixed-width integer type instead of a standard integer type, and state what the signed and unsigned fixed-width names guarantee when provided.
Why it matters: A known-width type can make a representation requirement, such as the size of a bit set, explicit. - Fixed width companion macros: Describe the macros supplied for provided fixed-width types and what they let a program specify or inspect.
Why it matters: These macros let code use a fixed-width type’s specified properties without guessing its underlying standard type. - Fixed width output formatting: Explain why formatting a fixed-width integer can be awkward and identify the C23 length specifiers described for it.
Why it matters: The width-based specifier avoids relying on an assumption about the typedef’s underlying type.
Key terms: <limits.h>, <stdint.h>, width, precision, uint(N)_t, int(N)_t, _WIDTH, _MIN, _MAX, _C
5.7.7 Bit-precise integer types
Introduces C23 bit-precise integers, their literals, width limits, and arithmetic behavior.
- Bit-precise type widths: Explain why C23 provides _BitInt types and state the supported width range.
Why it matters: Precise widths let you represent quantities whose bit requirements do not match the available exact-width types. - Bit-precise literal types: Describe how wb or WB integer-literal suffixes specify bit-precise types, including how signedness and the minimum sufficient width are determined.
Why it matters: The suffix gives a literal a bit-precise type without requiring a literal form specific to a separately named integer type. - Bit-precise arithmetic width: Explain the result width used when bit-precise integers are combined and how that can cause an unsigned result to wrap.
Why it matters: Choosing narrow integer types can change an arithmetic result through wraparound.
Key terms: _BitInt, bit-precise integer types, BITINT_MAXWIDTH, wb, u, unsigned _BitInt, signed _BitInt, maximal width of the operands, unsigned _BitInt(3), wraps around
5.7.8 Floating-point data
This section explains the limits and simplified binary representation of floating-point values, the consequences for arithmetic and comparison, and how complex values are represented and accessed.
- Floating-point range and precision: Explain why floating-point types cannot represent every mathematical real number: their range is limited, and expansions may have to be cut off. Distinguish DBL_MIN from the most negative double value.
Why it matters: It explains why a floating-point type is an approximation to mathematical real numbers, not an unlimited set of them. - Binary floating-point value model: Describe the section’s simplified formula for a floating-point value using a sign, exponent, and mantissa bits, and explain what precision counts. State why values in this model are fractions with a power of two in the denominator.
Why it matters: The model connects finite precision to the particular values a floating-point type can hold. - Floating-point arithmetic consequences: Explain how cutting off values during intermediate computations can affect results, especially when operands differ greatly in magnitude. Describe why the section warns against equality comparisons and says results may only be judged close.
Why it matters: It helps explain why calculations that seem mathematically equivalent may not give exactly matching floating-point results. - Complex value components: Describe how the section represents a complex type using two elements of its corresponding real floating-point type, and identify how to access its real and imaginary parts.
Why it matters: It relates C’s complex values to their real and imaginary floating-point components.
Key terms: floating-point types, DBL_MIN, DBL_MAX, sign, exponent, mantissa bits, precision, DBL_MANT_DIG, DBL_MIN_EXP, intermediate computations
5.8 Summary
Where this is used later
- Pure functions (chapter 10) · The abstract state machine and program state.
- Memory and state (chapter 12) · The abstract state machine and its state.
- (chapter intro) (chapter 21) · The abstract state machine view of program execution.
- Binary streams (chapter 14) · The distinction between values and their binary representations.
- A uniform memory model (chapter 12) · The section explicitly points ahead to observing the object representation of an object stored in memory.
- Digression: A machine model (chapter 13) · The as-if rule and the idea of compiler optimization.
- Measurement and inspection (chapter 16) · the compiler can optimize code
- Long jumps (chapter 19) · The idea that optimization can change how object values are accessed.
Concepts explained in this chapter
- floating-point arithmeticA floating-point value is a finite-precision numerical value represented by a sign, an exponent, and a sequence of significand bits.
- precisionPrecision is the number of significant binary digits available to represent a floating-point value in the C standard's restricted sense.
- integerAn integer type is a C type whose values represent whole numbers, with either signed or unsigned representation.
- binary representationA binary representation is an abstract, platform-specific model of the values of a C type that determines the results of operations on those values.
- abstract state machineAn abstract computational model that describes program execution through states and state transitions independently of a particular machine implementation.
- float64double is a C floating-point type providing at least the precision and range required for double-precision real-number calculations.
- complex floating-point typeA complex type is a C arithmetic type representing a complex number with real and imaginary components based on a corresponding real floating-point type.
- constexprconstexpr is a C23 language construct for declaring entities whose values or initialization satisfy compile-time constant-expression requirements.
- string_literalstring_literal is a book-specific function-like macro that accepts a string literal by requiring adjacent empty string literals in its replacement.
- bit-precise typeA bit-precise type is a C integer type with a precisely specified width in bits.
- implicit conversionAn implicit conversion is an automatic C conversion that changes a value from one type to another according to the language's conversion rules.
- basic typeA base type is the type from which a derived C type, such as a pointer type, is formed.
- literalA literal is source text that denotes a fixed value directly in a C program.
- binary literalA binary integer literal is a C23 integer literal written with a 0b or 0B prefix and binary digits.
- character literalA character literal is a C source-code literal enclosed in single quotes that represents an integer character code.
- decimal floating-point constantA decimal floating-point literal is a C floating-point literal written with decimal digits and optionally a fractional part and decimal exponent.
- decimal literalA decimal integer literal is a C integer literal written using decimal digits without a nondecimal base prefix.
- effective valueAn effective value is the value actually represented by a literal after the language's conversion and rounding rules are applied.
- hexadecimal floating-point constantA hexadecimal floating-point literal is a C floating-point literal written with a hexadecimal significand and a binary exponent introduced by P or p.
- hexadecimal numberA hexadecimal format represents a value using base-sixteen digits.
- literal suffixA literal suffix is a letter or letter sequence appended to a C literal to constrain or select its type.
- unsigned integerunsigned int is a C unsigned integer type that represents nonnegative whole numbers within an implementation-defined range.
- stateHidden state is implementation or library state that affects program behavior without being directly represented by ordinary program values.
- valueA value is the data represented by an object or expression at a particular point in program execution.
- program failureProgram failure is execution that can no longer be relied upon because the program has entered a state resulting from undefined behavior.
- sign bitA sign bit is the bit in a signed integer representation that indicates whether the represented value is nonnegative or negative.
- signed arithmetic overflowSigned arithmetic overflow is the condition in which a signed integer operation produces a result outside the range representable by its result type.
- signed integer arithmeticSigned arithmetic is arithmetic performed on values of signed integer types, with results subject to the rules for signed representations and overflow.
- two's complementTwo's complement is a signed-integer representation in which the high-order bit contributes negative weight and the remaining bits contribute positive weights.
- undefined behaviorUndefined behavior is a C execution condition for which the standard imposes no requirements on the program's subsequent behavior.
- enum constantAn enumeration constant is a named integer constant declared as a member of a C enumeration.
- char(n)char is a C character type that occupies exactly one byte and is intended to represent individual characters.
- implementation definedImplementation-defined behavior is behavior for which the C implementation chooses among permitted alternatives and documents its choice.
- integer promotionInteger promotion is the implicit conversion of a narrow integer type to an integer type before certain C expressions are evaluated.
- narrow typeA narrow type is a C integer type that undergoes integer promotion before being used in an arithmetic expression.
- ptrdiff_tptrdiff_t is a signed integer type capable of representing the difference between pointers to elements of the same array.
- time_ttime_t is an implementation-defined C type used to represent calendar time values.
- typedefA typedef is a C declaration that introduces an alias for an existing type.
- compound literalA compound literal is C syntax consisting of a type name in parentheses followed by an initializer that creates an unnamed object with the specified type and value.
- narrowing conversionA narrowing conversion is a conversion to a type that represents a smaller range or less precise set of values than the source type.
These guides are generated from the text of Manning books by Manning's knowledge graph and link to the sections they draw on. The full books are available on liveBook and at manning.com.
