Chapter 6: Derived data types
Introduces four ways C derives data types from basic types and distinguishes them from typedef, which gives an existing type a new name.
After this chapter you will be able to
- Array and structure aggregates. Distinguish arrays from structures as aggregate data types: arrays combine items of the same base type, while structures can combine items of different base types.
- Typedef as a type alias. Explain that typedef introduces a new name for an existing type rather than creating a new type in C’s type system.
- Array identity and grouping. Explain that an array groups objects of the same type into one encapsulating object and is not itself a pointer, even though arrays can look like pointers in many contexts.
- Array declarations and structure. Describe how an array declaration specifies its elements and how to read a nested array declaration.
- Nested array element access. Trace how subscripts access an element of an array and then an element of an array within it.
- Array objects without values. Explain that C has array objects but no array values, so arrays cannot be used for arithmetic, compared, or assigned to.
- Fixed array length rules. Explain how an FLA gets its length from a strictly positive integer constant expression or, when the brackets are empty, from an initializer.
- Variable array classification and restrictions. Distinguish a VLA from an FLA when an array length is not an integer constant expression, and state the restrictions given for VLA initialization and declaration location.
- Array length using sizeof. Apply the section’s sizeof expression to state how to compute the number of elements in an array.
- Array parameter dimension loss. Explain that an array parameter loses its innermost dimension, so sizeof should not be used on the parameter to determine the array’s size.
- Reference-like array parameter behavior. Describe how a function with an array parameter can act on the caller’s array rather than on a copy, as swap_double(A) does.
- String termination and storage. Explain that a C string is a char array terminated by a zero character, so its array length includes an element beyond the visible characters. Distinguish a char array from a string and note that strings can be initialized from literals but, like other arrays, cannot be assigned to.
- Array versus string functions. Distinguish the <string.h> mem functions, which operate on arrays using a supplied length, from str functions, which rely on string termination. Give examples of copying, comparing, or searching from each family.
- Non-string misuse and alternatives. Explain why passing a char array without known zero termination to a string function can cause program failure, and identify bounded array operations as safer alternatives when the array length is known.
- Pointers as opaque references. Explain that a pointer refers to data rather than directly containing the data of interest, and that its binary representation is platform-dependent. Describe why this section treats pointers only through permitted operations such as initialization, assignment, and evaluation.
- Pointer states and null tests. Distinguish valid, null, and invalid pointer states. Explain how a pointer becomes null, why null differs from pointing to an empty string, and what a logical test of a pointer can and cannot reveal.
- Uninitialized pointer danger. Explain why the section says to initialize pointers: an uninitialized pointer has an indeterminate state, and evaluating it can produce an invalid value and leave the program in an undefined state. State the section's fallback when validity cannot be ensured.
- When Structures Fit. Distinguish when an array is appropriate from when a structure is appropriate: arrays combine objects of the same base type where a first, second, and subsequent element make sense; structures are used when that ordering does not fit or the objects have different types.
- Named structure members. Explain how a structure type groups named members and how to distinguish declaring that type from defining an object of it. Describe how dot notation names a member when initializing or accessing an object.
- Structure initialization and omissions. Describe how named members can be initialized without relying on their positions, and what happens to members omitted from a structure initializer. Explain why zeroed omitted calendar fields may still leave the timestamp inconsistent.
- Structure values in functions. Explain why changing a member of a struct parameter does not change the caller's object, and how the example retains the change. State which whole-structure operations the section permits and forbids.
- Different types in one structure. Explain that a struct can group objects of different types into one enclosing object, as struct timespec does with a time_t seconds field and a long nanoseconds field.
- Nested structure members. Explain that a structure member may have another structure type, and that the enclosed structure type may be declared inside the outer structure. State that a VLA cannot be a structure member.
- Scope of enclosed declarations. Explain why declaring `struct stardate` inside `struct person` does not restrict its visibility to `person`, and describe what changes when the declarations are placed at the same level.
- Structure padding and ordering. Explain why a compiler may leave byte padding after structure members, why there is none at the beginning, and how member ordering can reduce wasted space.
- Bit-field storage and access. Describe how a bit-field declaration specifies a width, how successive bit-fields may share larger storage units, and how their fields are accessed.
- Bare int bit-field risk. Explain why a bare int bit-field may fail to represent an intended nonnegative range and why the section recommends unsigned instead.
- Type aliases and identity. Explain that typedef gives an existing type another name without creating a new type, including when the existing type is an array or structure.
- Same-name structure alias idiom. Describe the idiom of using a typedef with the same identifier as a structure tag, and explain why it helps when naming the structure type.
What it builds on
- Basic types (chapter 5) · Introduces the basic types from which these types are derived.
- Types (chapter 5) · Introduces types as a concept.
- Initializers (chapter 5) · The role of initializers in setting object values.
- Operands and operators (chapter 4) · The distinction between operands and operators.
- Enumerations (chapter 5) · The meaning of an integer constant expression.
- Function calls (chapter 2) · Assumes familiarity with calling a function with a parameter.
- Signed integers (chapter 5) · Signed integer values and their representable ranges.
- Bit-precise integer types (chapter 5) · Bit-precise integer types.
Section by section
Introduction
Introduces four ways C derives data types from basic types and distinguishes them from typedef, which gives an existing type a new name.
- Array and structure aggregates: Distinguish arrays from structures as aggregate data types: arrays combine items of the same base type, while structures can combine items of different base types.
Why it matters: This distinction explains which kind of aggregate fits the data you want to group. - Pointer and union strategies: Describe the introductory distinction between pointers and unions: a pointer refers to an object in memory, whereas a union overlays items of different base types at the same memory location.
Why it matters: The distinction keeps two different derived-type ideas from being confused. - Typedef as a type alias: Explain that typedef introduces a new name for an existing type rather than creating a new type in C’s type system.
Why it matters: It prevents a new type name from being mistaken for a distinct type.
Key terms: aggregate data types, arrays, structures, base type, pointers, unions, base types, typedef, new name, existing type
6.1 Arrays
Introduces arrays as objects that group elements of the same type and cautions that arrays are not pointers, despite their close relationship in C.
- Array identity and grouping: Explain that an array groups objects of the same type into one encapsulating object and is not itself a pointer, even though arrays can look like pointers in many contexts.
Why it matters: Keeping arrays distinct from pointers gives you a sound starting point for understanding both.
Key terms: arrays, pointers, objects, type
6.1.1 Array declaration
This section explains how array declarations describe elements and nested arrays, how subscripts reach their elements, and how designated initializers select positions.
- Array declarations and structure: Describe how an array declaration specifies its elements and how to read a nested array declaration.
Why it matters: Reading the declaration tells you what kind of object each array element is. - Nested array element access: Trace how subscripts access an element of an array and then an element of an array within it.
Why it matters: The type of the first selected element determines whether another subscript is needed. - Designated initializer positions: Explain what an array designated initializer selects and why it can help during development.
Why it matters: Naming an initialization position can make the code less fragile when an array changes.
Key terms: array, subobjects, base type, multidimensional array, array elements, subscript, C[0][0], designated initializers
6.1.2 Array operations
This section explains why arrays cannot be used as ordinary values in arithmetic, comparisons, or assignments, while noting that an array in a condition evaluates to true.
- Array objects without values: Explain that C has array objects but no array values, so arrays cannot be used for arithmetic, compared, or assigned to.
Why it matters: This distinction explains why several operations that work on other objects do not work on whole arrays. - Arrays in conditions: State what happens when an array is used in a condition and name the operation the section attributes this to.
Why it matters: It is a specific exception to the expectation that arrays behave like ordinary values in expressions.
Key terms: array objects, array values, value operators, object operators, condition, array decay
6.1.3 Array length
This section distinguishes fixed- and variable-length arrays, explains their length rules and restrictions, and shows how to calculate an array’s length with sizeof.
- Fixed array length rules: Explain how an FLA gets its length from a strictly positive integer constant expression or, when the brackets are empty, from an initializer.
Why it matters: These rules let you recognize when an array’s length is fixed at compile time. - Variable array classification and restrictions: Distinguish a VLA from an FLA when an array length is not an integer constant expression, and state the restrictions given for VLA initialization and declaration location.
Why it matters: Recognizing a VLA tells you which declaration restrictions apply. - VLA support across C versions: Describe how the required support for VLAs changed from C99 through C23, including the distinction C23 makes between automatic VLA objects and VLA types.
Why it matters: The C version affects which forms of VLA support an implementation must provide. - Array length using sizeof: Apply the section’s sizeof expression to state how to compute the number of elements in an array.
Why it matters: This gives you an element count from the array’s total size and one element’s size.
Key terms: FLA, integer constant expression, ICE, initializer, VLA, default initializers, C99, C11, C23, automatic objects
6.1.4 Arrays as parameters
This section explains why array parameters lose dimension information and why a function can change the caller’s array.
- Array parameter dimension loss: Explain that an array parameter loses its innermost dimension, so sizeof should not be used on the parameter to determine the array’s size.
Why it matters: An array-looking parameter does not retain all the size information you might expect. - Reference-like array parameter behavior: Describe how a function with an array parameter can act on the caller’s array rather than on a copy, as swap_double(A) does.
Why it matters: It explains why a function call can change elements of an array held by its caller.
Key terms: array parameter, innermost dimension, sizeof, passed by referenceC, swap_double(A)
6.1.5 Strings are special
This section explains what makes a char array a C string, how array and string library functions differ, and why missing termination can cause program failure.
- String termination and storage: Explain that a C string is a char array terminated by a zero character, so its array length includes an element beyond the visible characters. Distinguish a char array from a string and note that strings can be initialized from literals but, like other arrays, cannot be assigned to.
Why it matters: The terminating zero determines whether a char array can safely be used as a string. - Character encoding scope: Describe char as a narrow integer type capable of encoding C's basic character set, and explain why the particular encoding usually need not concern code that stays within that set.
Why it matters: It clarifies what the section assumes when treating strings as arrays of char. - Array versus string functions: Distinguish the <string.h> mem functions, which operate on arrays using a supplied length, from str functions, which rely on string termination. Give examples of copying, comparing, or searching from each family.
Why it matters: Choosing the right function family depends on whether the data is known to be a string. - String lengths and comparisons: Explain that strlen finds the first zero character rather than the array's full length, and describe how string comparison stops at termination and reports sort order.
Why it matters: A string's reported length and comparison range need not cover its whole array. - Non-string misuse and alternatives: Explain why passing a char array without known zero termination to a string function can cause program failure, and identify bounded array operations as safer alternatives when the array length is known.
Why it matters: Checking termination or using bounded operations prevents scans and copies from running past the array.
Key terms: string, char, 0-terminated, string literal, basic character set, ASCII, <string.h>, memcpy, memcmp, memchr
6.2 Pointers as opaque types
This section introduces pointers as opaque references to data, explains their valid, null, and invalid states, and shows why they should always be initialized.
- Pointers as opaque references: Explain that a pointer refers to data rather than directly containing the data of interest, and that its binary representation is platform-dependent. Describe why this section treats pointers only through permitted operations such as initialization, assignment, and evaluation.
Why it matters: This gives you a useful way to think about pointers without relying on how a platform represents them. - Pointer states and null tests: Distinguish valid, null, and invalid pointer states. Explain how a pointer becomes null, why null differs from pointing to an empty string, and what a logical test of a pointer can and cannot reveal.
Why it matters: A null check tells you whether a pointer is null, not whether every non-null pointer is safe. - Uninitialized pointer danger: Explain why the section says to initialize pointers: an uninitialized pointer has an indeterminate state, and evaluating it can produce an invalid value and leave the program in an undefined state. State the section's fallback when validity cannot be ensured.
Why it matters: Initializing a pointer avoids leaving it in the unobservable invalid state described here.
Key terms: pointer, opaque objects, initialization, assignment, evaluation, valid, null, invalid, nullptr, null pointer
6.3 Structures
This section introduces structures as a way to combine objects when array-style ordering is unsuitable or the objects have different types.
- When Structures Fit: Distinguish when an array is appropriate from when a structure is appropriate: arrays combine objects of the same base type where a first, second, and subsequent element make sense; structures are used when that ordering does not fit or the objects have different types.
Why it matters: This distinction helps you choose a way to group data that fits what the objects represent.
Key terms: arrays, base type, structures, struct
6.3.1 Simple structures to access fields by name
The section introduces named structure members through bird and calendar-time examples, then explains structure initialization, value passing, assignment, comparison limits, and the lasting importance of layout decisions.
- Named structure members: Explain how a structure type groups named members and how to distinguish declaring that type from defining an object of it. Describe how dot notation names a member when initializing or accessing an object.
Why it matters: Member names make the meaning of each piece of data clearer than its position in an array. - Structure initialization and omissions: Describe how named members can be initialized without relying on their positions, and what happens to members omitted from a structure initializer. Explain why zeroed omitted calendar fields may still leave the timestamp inconsistent.
Why it matters: Automatic zero initialization does not guarantee that related fields describe a consistent value. - Structure values in functions: Explain why changing a member of a struct parameter does not change the caller's object, and how the example retains the change. State which whole-structure operations the section permits and forbids.
Why it matters: It explains both why a function's change may appear to be lost and how to preserve it without confusing assignment with comparison. - Lasting structure layout choices: Explain why choosing a structure's members is an important design decision that may be difficult to revise later.
Why it matters: A convenient member layout today can constrain code that depends on it for years.
Key terms: struct, members, fields, dot (.), struct birdStruct, struct initializer, struct tm, tm_wday, tm_yday, {}
6.3.2 Structures with fields of different types
This subsection shows that a struct can combine fields of different types, using nanosecond-resolution times as an example.
- Different types in one structure: Explain that a struct can group objects of different types into one enclosing object, as struct timespec does with a time_t seconds field and a long nanoseconds field.
Why it matters: This lets a single object represent related values even when those values need different types. - Timespec difference implementation: Distinguish the standard C function difftime from a function to compute the difference between two struct timespec times, which the section says is simple enough to implement without platform-specific properties.
Why it matters: It clarifies why the example can use an ordinary user-defined function rather than rely on a platform-specific facility.
Key terms: struct, struct timespec, time_t, long, difftime, platform-specific properties
6.3.3 Nested structures
The section explains that structures can contain other structures and that declaring one structure inside another does not give it a separate scope.
- Nested structure members: Explain that a structure member may have another structure type, and that the enclosed structure type may be declared inside the outer structure. State that a VLA cannot be a structure member.
Why it matters: This lets you recognize a structure that contains another structure without treating it as a special kind of member. - Scope of enclosed declarations: Explain why declaring `struct stardate` inside `struct person` does not restrict its visibility to `person`, and describe what changes when the declarations are placed at the same level.
Why it matters: The nesting in the declaration does not hide the enclosed structure type or require a different structural layout.
Key terms: structure member, nested structures, VLA, scope of visibility, nested declaration, compound statement, layout
6.3.4 Coalescing structure fields
This section explains how structure padding and bit-fields affect storage, and how to choose bit-field types that represent values reliably.
- Structure padding and ordering: Explain why a compiler may leave byte padding after structure members, why there is none at the beginning, and how member ordering can reduce wasted space.
Why it matters: Member order can affect a structure’s storage without changing the order in which its fields are declared. - Bit-field storage and access: Describe how a bit-field declaration specifies a width, how successive bit-fields may share larger storage units, and how their fields are accessed.
Why it matters: Bit-fields can avoid allocating a full ordinary member for every small value without changing basic field access. - Bare int bit-field risk: Explain why a bare int bit-field may fail to represent an intended nonnegative range and why the section recommends unsigned instead.
Why it matters: An apparently adequate bit count can silently be inadequate if a bit-field is signed. - Explicit bit-field types: Describe the section’s recommendations to use a _BitInt(N) type for a numerical bit-field of width N and bool for a one-bit flag.
Why it matters: Explicit types make the intended interpretation of compact numerical fields and flags clearer.
Key terms: alignment, byte padding, structure member, bit padding, bit-field, coalescing, member designator, designated initializer, bare int, signed
6.4 New names for types: Type aliases
This section explains how typedef gives existing C types alternative names, shows an idiom for naming structures, and warns against defining names ending in _t.
- Type aliases and identity: Explain that typedef gives an existing type another name without creating a new type, including when the existing type is an array or structure.
Why it matters: It helps you read a typedef name without mistaking it for a distinct type. - Same-name structure alias idiom: Describe the idiom of using a typedef with the same identifier as a structure tag, and explain why it helps when naming the structure type.
Why it matters: This idiom avoids repeated struct keywords and the errors caused by forgetting one. - Reserved typedef name suffix: State why code should not introduce identifiers ending in _t, in light of the standard library’s typedef naming convention.
Why it matters: Avoiding _t-ending names prevents conflicts with names the C standard may introduce.
Key terms: typedef, alias, type, struct, tag, forward declaration, _t, size_t, reserved
6.5 Summary
Where this is used later
- Unions (chapter 12) · Introduces unions in the context of the memory model.
- Pointers and arrays (chapter 11) · The later discussion of pointers and arrays develops the relationship introduced here.
- Recursion (chapter 7) · An array can hold previously computed values.
- Digression: Using objects before their definition (chapter 13) · The earlier treatment of array declarations.
- Arithmetic violations (chapter 15) · Accessing array elements.
- Pointer addition (chapter 11) · It contrasts pointers with the earlier sizeof technique for array length.
- A uniform memory model (chapter 12) · The reader has previously encountered sizeof in the context of arrays.
- Array and pointer parameters are the same (chapter 11) · Explains the pointer-related mechanism behind array parameters.
Concepts explained in this chapter
- attributeAn attribute is supplemental implementation or semantic information attached to a C program construct.
- structThe struct keyword introduces or refers to a C structure type through its structure tag.
- memchrmemchr is a C standard-library function that searches a specified memory region for a byte value.
- memcpymemcpy is a C standard-library function that copies a specified number of bytes from one nonoverlapping memory region to another.
- prototypeA function prototype is a declaration that specifies a function's name, return type, and parameter types without providing its body.
- strcmpstrcmp is a C standard-library function that lexicographically compares two null-terminated strings.
- strcpystrcpy is a C standard-library function that copies a null-terminated string into destination storage.
- stringA C string is an array of char whose elements end with a terminating zero character.
- string.hstring.h is the C standard header that declares functions for manipulating strings and related memory sequences.
- strlenstrlen is a C standard-library function that returns the number of characters before the terminating zero in a string.
- alignmentAlignment is the implementation-defined requirement that an object or structure member begin at an address suitable for its type.
- bit fieldA bit-field is a C structure or union member declared with a specified number of bits rather than a complete storage-unit width.
- bit paddingBit padding is unused storage at the bit level within a representation, including bits that do not fall on byte boundaries.
- paddingByte padding is unused whole-byte storage inserted within or after a structure to satisfy layout or alignment requirements.
- struct tmstruct tm is the C standard-library structure type for representing broken-down calendar time components such as seconds, days, months, and years.
- arrayAn array is an aggregate object containing a fixed sequence of items of the same type, accessed by numeric indices.
- referenceA pointer type is a C type whose values designate objects or functions in storage.
- typedefA typedef is a C declaration that introduces an alias for an existing type.
- sizeof operatorThe sizeof operator is a C unary operator that yields the size in bytes of a type or object representation.
- missing valuenullptr is the C23 null pointer constant used to represent a null pointer value.
- invalid pointerAn invalid pointer is a pointer value whose state does not permit reliable evaluation or use according to the C language rules.
- opaque objectAn opaque structure is a C structure type whose complete member definition is hidden from code that uses its interface.
- array lengthArray length is the number of elements contained in an array object.
- variable-length arrayA variable-length array is a C array whose bound is determined at run time rather than being an integer constant expression.
- public visibilityVisibility is the property determining whether an identifier can be referenced from a particular part of a program.
- scopeA scope is a region of a C program within which an identifier is visible and its declaration applies.
- array parameterAn array parameter is a function parameter declared with array notation whose array-bound information is adjusted or lost when used in a function definition or call.
- struct timespecstruct timespec is a C structure type representing a time value with seconds and a nanosecond component.
- strcspnstrcspn is a C standard-library function that returns the length of the initial segment containing none of the characters from a specified string.
- strdupstrdup is a C library function that allocates storage and copies a null-terminated string into it.
- strndupstrndup is a C23 library function that allocates storage and copies at most a specified number of characters from a string.
- strspnstrspn is a C standard-library function that returns the length of the initial segment containing only characters from a specified string.
- integerAn integer type is a C type whose values represent whole numbers, with either signed or unsigned representation.
- time.htime.h is the C standard header that declares calendar-time types and functions, including struct tm and related interfaces.
- valid pointerA valid pointer is a pointer value whose state permits the pointer operations defined by the C language.
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.
