Arrays, Strings, Records, and Enumerated DataIntroductionEngineering programs rarely process one value at a time. A temperature logger may produce thousands of readings, a circuit simulator may store a matrix of node coefficients, and a motor controller may maintain a structured record containing speed, current, voltage, and operating state. Programming therefore requires data representations that preserve relationships among related values while allowing efficient processing. This chapter introduces four important forms of structured data in C: arrays, strings, structures (records), and enumerations. It also explains multidimensional arrays, standard library operations, memory considerations, validation, and the design of an engineering application combining these forms. The examples use standard C. Array indexing begins at zero, and an array of length n has valid indices from 0 through n − 1. C does not automatically check whether an index is within bounds; safe programs must perform that checking explicitly. Learning ObjectivesAfter studying this chapter, you should be able to: - Declare, initialize, access, and process one- and multidimensional arrays.
- Explain array storage, indexing, traversal, and common errors.
- Represent and manipulate strings safely using character arrays and the C standard library.
- Define and use structures to model engineering records.
- Use enumerated types to represent a finite set of named states.
- Select suitable data representations for engineering applications.
- Design functions that process collections and structured data safely and clearly.
1. Arrays as Collections of Homogeneous ValuesAn array is a fixed-size sequence of elements of the same type stored contiguously in memory. The type and size are part of its declaration: int samples[8]; This declaration reserves space for eight integers. The first element is samples[0], and the last is samples[7]. An array can be initialized when declared: int samples[5] = {12, 15, 11, 18, 14};If the size is omitted, the compiler determines it from the initializer: int samples[] = {12, 15, 11};Partial initialization sets remaining elements to zero: float gains[4] = {2.0f, 1.5f}; /* becomes {2.0, 1.5, 0.0, 0.0} */The size of an array in bytes is commonly obtained with sizeof: size_t count = sizeof samples / sizeof samples[0]; This expression is valid where samples is an actual array, not after it has been passed to a function, because function parameters declared as arrays are adjusted to pointers. TraversalThe most common operation is traversal using a loop: #include <stdio.h>
int main(void) {
int samples[] = {12, 15, 11, 18, 14};
size_t n = sizeof samples / sizeof samples[0];
for (size_t i = 0; i < n; ++i) {
printf("%d\n", samples[i]);
}
return 0;
}A robust loop uses i < n, not i <= n. The latter attempts to access one element beyond the array. Engineering statistics from an arraySuppose an array contains n voltage samples, measured in volts. Their arithmetic mean is: V̄ = (1/n) ∑ᵢ₌₀ⁿ⁻¹ Vᵢ Here, Vᵢ is sample i in volts, n is the number of samples, and V̄ is the mean voltage in volts. The formula assumes that all samples represent the same quantity and use the same unit. #include <stddef.h>
double mean(const double values[], size_t n) {
if (n == 0) return 0.0; /* caller must interpret this case */
double sum = 0.0;
for (size_t i = 0; i < n; ++i) {
sum += values[i];
}
return sum / (double)n;
}The function has linear time complexity, written O(n), because each element is visited once. It uses constant additional storage, O(1), apart from the input array. A zero-length input has no arithmetic mean. Returning 0.0 is a design choice that may be unsuitable when zero is a meaningful engineering value. In production software, a status code, Boolean result, or optional-result design may communicate invalid input more clearly. 2. Multidimensional ArraysA multidimensional array is an array whose elements are themselves arrays. A two-dimensional array is useful for tables, images, sampled waveforms, and matrix calculations: double temperature[3][4]; This represents three rows and four columns. The element in row r, column c is temperature[r][c], where 0 ≤ r < 3 and 0 ≤ c < 4. C stores the rows contiguously in row-major order. Thus, in temperature[3][4], all four elements of row zero are followed by all four elements of row one. This matters for cache efficiency and for passing arrays to functions. void print_matrix(size_t rows, size_t cols,
const double matrix[rows][cols]) {
for (size_t r = 0; r < rows; ++r) {
for (size_t c = 0; c < cols; ++c) {
printf("%8.2f", matrix[r][c]);
}
putchar('\n');
}
}The notation above uses a variable-length array parameter. The dimensions must be available when the function is called. The const qualifier indicates that the function will not modify the matrix. A matrix element can be updated using nested loops: void scale_matrix(size_t rows, size_t cols,
double matrix[rows][cols], double factor) {
for (size_t r = 0; r < rows; ++r)
for (size_t c = 0; c < cols; ++c)
matrix[r][c] *= factor;
}For matrix addition, two matrices must have equal dimensions. If A and B are m × n matrices, their sum is defined element by element: Cᵢⱼ = Aᵢⱼ + Bᵢⱼ The indices satisfy 0 ≤ i < m and 0 ≤ j < n. Matrix multiplication has different dimension requirements and should not be confused with elementwise multiplication. 3. Passing Arrays to FunctionsWhen an array is passed to a function, C normally passes the address of its first element. The function can therefore modify the original array: void zero_array(int a[], size_t n) {
for (size_t i = 0; i < n; ++i)
a[i] = 0;
}The parameter int a[] is treated essentially as int *a; it does not carry the array length. The caller must pass the length separately. A safer interface documents the relationship between the pointer and the count, and uses const for read-only input: int maximum(const int a[], size_t n, int *result) {
if (n == 0 || result == NULL) return 0;
int max = a[0];
for (size_t i = 1; i < n; ++i)
if (a[i] > max) max = a[i];
*result = max;
return 1;
}This function returns success or failure and writes the result through an output pointer. It avoids pretending that a maximum exists for an empty collection. Common array errors include out-of-range indexing, using uninitialized elements, confusing the number of bytes with the number of elements, and returning a pointer to a local array. A local array ceases to exist when its function returns; dynamically allocated or caller-provided storage is required for returned collections. 4. Strings in CC has no built-in string type. A string is a sequence of characters terminated by the null character \0. For example: char name[] = "Sensor A"; The array contains nine characters including the terminating null character: eight visible characters and one terminator. Therefore, a character array must have room for the terminator. char code[5] = "AB12"; /* valid: four characters plus '\0' */
char word[4] = "ABCD"; /* not a C string: no room for '\0' */ The string length excludes the terminator. The library function strlen returns the number of characters before \0: #include <string.h>
size_t length = strlen(name); Important functions in <string.h> include: | Function | Purpose | Important condition |
|---|
strlen(s) | Counts characters | s must be null-terminated | strcmp(a,b) | Lexicographic comparison | Both arguments must be strings | strcpy(dest,src) | Copies a string | Destination must have sufficient space | strncpy(dest,src,n) | Copies up to n characters | May not append \0 | strcat(dest,src) | Appends a string | Destination must have sufficient remaining space | snprintf | Formatted bounded output | Buffer size must be supplied |
The functions strcpy and strcat are dangerous when destination capacity is not controlled. A preferred technique is bounded formatted output: char message[64];
int written = snprintf(message, sizeof message,
"Channel %d: %.2f V", 2, 3.30);
if (written < 0 || (size_t)written >= sizeof message) {
/* encoding error or truncation */
}snprintf attempts to write no more than the supplied capacity, including the terminator. Its return value indicates the number of characters that would have been written, excluding the terminator. A return value at least as large as the buffer capacity indicates truncation.
Input and comparisonscanf("%s", buffer) without a field width can overflow buffer. For line-oriented input, fgets is generally safer:
char line[80];
if (fgets(line, sizeof line, stdin) != NULL) {
line[strcspn(line, "\n")] = '\0';
}strcmp returns zero when strings are equal; it does not return a Boolean value that should be compared only with one. The correct test is:
if (strcmp(command, "START") == 0) {
/* process command */
}String processing is vulnerable to buffer overflow, unterminated strings, encoding assumptions, and accidental exposure of confidential information. Input length, permitted characters, and expected encoding should be specified as part of an engineering system’s requirements. 5. Structures: Records for Heterogeneous DataAn array groups values of one type. A structure groups fields that may have different types: struct SensorReading {
unsigned int channel;
double voltage;
double current;
char unit[8];
};A variable can be declared as follows: struct SensorReading reading = {2, 3.30, 0.125, "SI"};Fields are accessed with the dot operator: printf("Channel %u: %.2f V\n", reading.channel, reading.voltage);A pointer to a structure uses the arrow operator: struct SensorReading *p = &reading;
p->current = 0.130; A type alias often improves readability: typedef struct {
unsigned int channel;
double voltage;
double current;
} SensorReading;Structures can be nested and can form arrays: SensorReading logbook[100]; This represents up to 100 readings, each containing several related fields. A structure is therefore a natural representation for a row in a data log or a message in an embedded system. Structure layout and paddingThe size of a structure may exceed the sum of the nominal field sizes because the compiler can insert padding for alignment. Consequently, sizeof(struct SensorReading) should be used rather than manually adding field sizes. Binary serialization must not assume that the in-memory layout is portable across processors or compilers. A defined external format should specify field order, widths, byte order, and encoding. Functions can receive structures by value or by pointer. Passing a pointer avoids copying large records: void calibrate(SensorReading *r, double offset) {
if (r != NULL)
r->voltage += offset;
}Use const SensorReading * when a function only observes the record. This communicates intent and helps prevent accidental modification. 6. Enumerated DataAn enumeration gives names to a finite set of integral values: typedef enum {
SENSOR_OFF,
SENSOR_READY,
SENSOR_FAULT
} SensorState;Unless explicit values are supplied, the constants begin at zero and increase by one. A variable can then be declared as: SensorState state = SENSOR_READY; Enumerations improve readability and reduce the use of unexplained “magic numbers.” They are especially useful for operating modes, alarm conditions, communication states, and units. switch (state) {
case SENSOR_OFF:
puts("Sensor is off");
break;
case SENSOR_READY:
puts("Sensor is ready");
break;
case SENSOR_FAULT:
puts("Sensor fault");
break;
default:
puts("Invalid state");
break;
}The default branch is valuable because data from hardware, files, or communication links may contain values outside the intended set. Enumeration constants are represented by integer values, but programs should not assume that an enumeration occupies a particular number of bytes or that every integer is semantically valid. Explicit values are appropriate when an external protocol specifies codes: typedef enum {
COMMAND_STOP = 0x10,
COMMAND_START = 0x20,
COMMAND_RESET = 0x30
} CommandCode;The enumeration improves source-level meaning, but validation is still required when converting an untrusted integer into a command code. 7. Combining Data Types in an Engineering ModelThe following record combines an array-related measurement, a string identifier, and an enumerated state: typedef enum {
OFF,
RUNNING,
ALARM
} MotorState;
typedef struct {
char id[16];
double speed_rpm;
double current_a;
double temperature_c;
MotorState state;
} Motor;A collection of motors can be processed as an array of structures: void stop_overheated(Motor motors[], size_t count,
double limit_c) {
for (size_t i = 0; i < count; ++i) {
if (motors[i].temperature_c > limit_c)
motors[i].state = ALARM;
}
}The comparison is meaningful only if every temperature is measured on the same scale and the limit has the same unit. The program should also define how missing, invalid, or sensor-fault values are represented; silently treating an invalid reading as zero can produce unsafe decisions. 8. Worked Application: Motor Test-Bench LoggerBackgroundThe following illustrative case concerns a small laboratory motor test bench. Each test produces 60 current samples at one-second intervals. The system also records the motor identifier, average current, maximum current, and state. ProblemDesign data structures and processing functions that: - store the samples;
- calculate average current;
- find the maximum current;
- assign an alarm state if the maximum exceeds 8.0 A;
- produce a readable report.
AnalysisA fixed array is suitable because the test duration is known. A structure groups the test metadata and results. An enumeration expresses the finite state set. #include <stdio.h>
#include <stddef.h>
#define SAMPLE_COUNT 60
typedef enum {
TEST_NORMAL,
TEST_OVERCURRENT
} TestState;
typedef struct {
char motor_id[16];
double current_a[SAMPLE_COUNT];
double average_a;
double maximum_a;
TestState state;
} MotorTest;The processing function validates its pointer and computes both statistics in one pass: int analyze_test(MotorTest *test) {
if (test == NULL) return 0;
double sum = 0.0;
double maximum = test->current_a[0];
for (size_t i = 0; i < SAMPLE_COUNT; ++i) {
sum += test->current_a[i];
if (test->current_a[i] > maximum)
maximum = test->current_a[i];
}
test->average_a = sum / SAMPLE_COUNT;
test->maximum_a = maximum;
test->state = (maximum > 8.0) ? TEST_OVERCURRENT : TEST_NORMAL;
return 1;
}For n = 60 samples, the average current is: Ī = (1/60) ∑ᵢ₌₀⁵⁹ Iᵢ The result is in amperes because each Iᵢ is in amperes and division is by a dimensionless count. The alarm rule uses the strict condition Iₘₐₓ > 8.0 A. A reading exactly equal to 8.0 A is normal under this stated policy; a different safety specification could use ≥ instead. OutcomeThe design stores raw samples for later analysis while retaining summary values for reporting. It has linear processing time and fixed storage. In a real test bench, additional requirements would include timestamp accuracy, sensor calibration, treatment of missing samples, persistent storage, access control, and independent verification of the alarm threshold. Learning pointsThe case demonstrates that data representation is part of system design. Arrays preserve a sequence, structures preserve relationships, strings identify objects, and enumerations constrain operating states. Combining them produces a model closer to the engineering problem than a collection of unrelated variables. 9. Standard Library Operations and Design QualityThe C standard library supports common operations, but library use does not remove the need for reasoning about capacity and validity. Useful headers include: | Header | Relevant facilities |
|---|
<stddef.h> | size_t, NULL | <string.h> | string and memory functions | <stdio.h> | formatted input and output, fgets, snprintf | <stdlib.h> | allocation and conversion functions | <stdbool.h> | Boolean type and constants | <ctype.h> | character classification and conversion |
memcpy copies a specified number of bytes but requires non-overlapping regions. memmove supports overlapping regions. Neither function understands string terminators or structure meaning. memcmp compares bytes, not necessarily logical values; padding bytes in structures can make byte comparison inappropriate.
Dynamic arrays can be allocated when the required size is known only at run time: #include <stdlib.h>
size_t n = 100;
double *values = malloc(n * sizeof *values);
if (values == NULL) {
/* allocation failed */
} else {
/* use values[0] through values[n - 1] */
free(values);
}The multiplication used for allocation should be checked for integer overflow when sizes originate from external input. Every successful allocation must have one corresponding free, and the pointer must not be used after it is freed. 10. Challenges, Limitations, and SafetyArrays are efficient but fixed-size unless combined with dynamic allocation. C strings are compact but depend on a terminator and provide no automatic bounds checking. Structures express data relationships but may contain padding and are not automatically portable as file formats. Enumerations improve semantic clarity but do not guarantee that input values are valid. For safety-critical or security-sensitive engineering software, developers should establish coding standards, perform static analysis, test boundary conditions, and document assumptions. Important tests include empty collections, maximum lengths, exact threshold values, invalid enumeration values, null pointers, allocation failure, and malformed input. Sensitive records should not be printed indiscriminately. Identifiers, device locations, and diagnostic logs may reveal operational information. Data retention, access permissions, and audit requirements should be addressed where the application handles industrial or personal data. 11. Contemporary Practice and EmployabilityModern engineering development frequently combines C with automated testing, version control, continuous integration, static analyzers, and hardware-in-the-loop simulation. C++ offers standard containers such as std::array, std::vector, and std::string; Python and MATLAB provide higher-level array abstractions. Nevertheless, understanding C arrays and memory remains valuable for embedded systems, device drivers, real-time control, and interfacing with hardware. AI-assisted programming tools can suggest array-processing code, but generated code must be reviewed for bounds errors, incorrect units, unsafe string handling, and inappropriate assumptions about sensor data. An engineer remains responsible for requirements, verification, and safety decisions. Chapter SummaryArrays store ordered collections of homogeneous values and are accessed using zero-based indices. Their size and bounds must be managed explicitly. Multidimensional arrays represent tables and matrices, with C using row-major storage. Arrays passed to functions lose their length information, so a pointer and element count should normally be supplied together. C strings are null-terminated character arrays. Functions such as strlen, strcmp, and snprintf are useful, but capacity and termination must be controlled. Structures group heterogeneous fields into meaningful engineering records, while enumerations give names to finite sets of states or codes. Arrays, strings, structures, and enumerations can be combined to represent realistic systems such as sensor logs and motor test results. Reliable implementations validate sizes, pointers, input values, units, and boundary conditions. They use const to communicate read-only data, avoid unsafe unbounded string operations, document assumptions, and distinguish invalid or missing measurements from valid numerical zeros. Key TermsArray: A fixed-size contiguous collection of elements of one type. Index: An integer position used to access an array element. Multidimensional array: An array whose elements are arrays, commonly used for tables and matrices. Row-major order: Storage arrangement in which complete rows are stored consecutively. Traversal: Systematic processing of each element in a collection. String: A null-terminated sequence of characters in C. Null character: The character \0 marking the end of a C string. Structure: A user-defined type grouping fields that may have different types. Record: A conceptual collection of related fields, commonly implemented with a structure. Enumeration: A user-defined type containing named integral constants. size_t: An unsigned type used for object sizes and array counts.
Pointer: An object containing the address of another object. Bounds checking: Verifying that an index lies within the valid range. Padding: Compiler-inserted bytes used to satisfy alignment requirements. const: A qualifier indicating that an object should not be modified through a particular access path.
Lexicographic comparison: Ordering strings according to character sequence. Dynamic allocation: Obtaining storage during program execution, commonly with malloc. Buffer overflow: Writing beyond the allocated capacity of an object. Serialization: Converting structured data into a defined representation for storage or transmission. Review QuestionsShort Answer Questions- Why is the last valid index of an array with 12 elements equal to 11?
- What is the purpose of the null character in a C string?
- Why should an array length normally be passed separately to a function?
- State one advantage of an enumeration over unexplained integer constants.
- What is the difference between the dot operator and the arrow operator for structures?
Long Answer Questions- Explain how one-dimensional and two-dimensional arrays are declared, initialized, traversed, and passed to functions in C.
- Discuss safe string processing in C, including termination, capacity, comparison, input, and formatted output.
- Explain structure layout, padding, pointers to structures, and the limitations of treating a structure’s memory as a portable file format.
- Compare arrays, structures, and enumerations as representations of engineering data, using examples.
- Design a data model for a temperature-monitoring system containing sensor identity, samples, calibration information, and alarm state. Justify each type selected.
Analytical / Application Questions- A data logger receives 24 temperature readings but occasionally receives fewer because of communication loss. Design a function interface that distinguishes valid readings from missing readings and explain how the average should be computed.
- An embedded controller stores 20 motor records and must set an alarm when current exceeds a specified limit. Propose suitable structures and enumeration values, and identify at least four boundary or invalid-input tests.
- A program copies an incoming device name into
char name[16] using an unbounded string function. Analyze the security risk and propose a replacement that detects truncation.
Multiple Choice Questions with AnswersQuestion 1An array declared as double voltage[8] has which highest valid index? A. 7 B. 8 C. 9 D. 0 Correct Answer: A. 7 Question 2What marks the end of a C string? A. A space character B. The null character \\0 C. A newline character only D. The character EOF Correct Answer: B. The null character \\0 Question 3Which expression correctly determines the number of elements in an actual array a? A. sizeof(a) B. sizeof(a[0]) C. sizeof(a) / sizeof(a[0]) D. sizeof(a) * sizeof(a[0]) Correct Answer: C. sizeof(a) / sizeof(a[0]) Question 4What does strcmp(a, b) == 0 indicate? A. a is longer than b B. The strings contain equal character sequences C. The strings have different lengths necessarily D. b is alphabetically before a Correct Answer: B. The strings contain equal character sequences Question 5Which operator accesses a structure member through a structure pointer? A. . B. & C. -> D. * Correct Answer: C. -> Question 6Why is an enumeration useful in an engineering program? A. It automatically validates all external input B. It gives meaningful names to a finite set of values C. It stores arbitrary-length strings D. It guarantees a fixed memory size on every platform Correct Answer: B. It gives meaningful names to a finite set of values Question 7For a two-dimensional C array declared int m[3][4], which access is valid for the final element? A. m[3][4] B. m[2][4] C. m[3][3] D. m[2][3] Correct Answer: D. m[2][3] Question 8What does snprintf help control? A. The maximum number of bytes written to a destination buffer B. The number of enumeration constants C. The row-major ordering of an array D. The alignment of every structure field Correct Answer: A. The maximum number of bytes written to a destination buffer Question 9If a test contains 60 current samples, which expression represents the arithmetic mean? A. The maximum sample divided by 60 B. The sum of all samples divided by 60 C. 60 divided by the sum of all samples D. The first sample multiplied by 60 Correct Answer: B. The sum of all samples divided by 60 Question 10Which statement about an array parameter such as int a[] in a C function is correct? A. The function automatically knows the caller’s array length B. The parameter is treated essentially as a pointer, so length information is not carried automatically C. The array is copied completely on every call D. The parameter prevents all out-of-bounds access Correct Answer: B. The parameter is treated essentially as a pointer, so length information is not carried automatically
|