Arrays and Strings in C

Learn how C stores many values in one array and how it represents text as characters ending in a null byte. Loop over arrays and tables, pass them to functions, process strings with your own loops and with the standard string functions, see how UTF-8 text is stored in bytes, and read the arguments a program receives on the command line. Throughout, learn why C never checks array bounds and how to read and copy data so it always fits.

BeginnerLesson 3 of 8 in Programming in C Course

What you will learn

  1. Declare arrays of a fixed size, reach each element by its index, loop over them without stepping past the end, and share them with functions.
    • Declare, initialize and index an array
      Create arrays with a fixed number of elements, give them starting values, and read or change single elements by their index.
    • Loop through arrays without going out of bounds
      Visit every element with a counting loop and use that pattern to sum, find the largest value, search, count and reverse.
    • Pass arrays to functions
      Write functions that take arrays as parameters, pass the length alongside, and know when the function can change the caller's data.
  2. Treat characters as small integer codes, store text in char arrays that end with a null character, process it one character at a time, and see how UTF-8 text fits into bytes.
    • Treat characters as numeric codes
      Use the char type and character constants as numbers, convert digits and letters with arithmetic, and classify characters with <ctype.h>.
    • Store text as null-terminated strings
      Explain how a C string is laid out in a char array, why it needs a terminating null character, and how string literals and arrays differ.
    • Process strings one character at a time
      Walk through a string until its terminator to count, convert, reverse, filter and encrypt text, and finish every new string with '\0'.
    • Explain how UTF-8 text is stored in char arrays
      Relate characters, Unicode code points and UTF-8 bytes, and predict what byte-based C functions do with text beyond ASCII.
  3. Measure, copy, join, compare and search strings with <string.h>, and make sure every destination has room for the result and its terminator.
    • Measure, copy and join strings
      Use strlen, strcpy and strcat correctly and work out how much room each destination needs.
    • Compare and search strings
      Compare strings with strcmp and strncmp, find characters and substrings, and split text into tokens.
    • Copy within limits and work with raw memory
      Use the bounded functions strncpy, strncat and snprintf correctly, detect truncation, and copy, fill and compare whole arrays with the memory functions.
  4. Store tables and lists of strings in arrays with two or more dimensions, traverse them with nested loops, and pass them to functions.
    • Declare and traverse two-dimensional arrays
      Declare and initialize tables of values, explain how their rows are laid out in memory, and process rows and columns with nested loops.
    • Pass tables to functions and store lists of strings
      Write functions that take two-dimensional arrays, and keep several strings either in a 2D char array or in an array of string pointers.
  5. Read the words typed after a program's name through argc and argv, check how many there are, and turn them safely into numbers and options.
    • Read argc and argv
      Declare main with its two parameters, describe what each element of argv holds, and check the argument count before using any argument.
    • Convert and validate numeric arguments
      Turn argument strings into numbers with error checking, reject invalid input, and range-check values before using them.
  6. Recognize how C programs read and write outside their arrays, why this is dangerous, and how to read and copy text so that it always fits.
    • Recognize out-of-bounds access and its effects
      Explain why C does not stop out-of-bounds access, what can happen when it occurs, and which mistakes cause it.
    • Read and copy text within buffer limits
      Read input with size limits, detect lines that are too long, and copy text only when it fits its destination.

Arrays and Strings in C study guide

6 chapters

1

Store and process collections of values with arrays

Arrays let one loop work on many related values. The array fixes how much storage exists, while indexes select individual elements and a count records how many are in use. Learn how declarations establish starting values, how loops compute results without crossing a bound, and how functions share the caller’s elements. Small examples connect the code to the exact positions it reads or writes.

1.1 Declare, initialize and index an array

An array gives one name to a fixed number of values of the same type. Its elements sit next to each other in memory. Use an array when the same operation applies to many values, such as updating a set of scores.

Create the elements
int scores[5] = {90, 72, 85, 64, 99};
int primes[] = {2, 3, 5, 7};
int counts[6] = {0};

The first declaration creates five integers. The compiler counts four values in the second initializer, so primes has four elements. In the third, the first element is explicitly zero and every missing initializer also becomes zero. Similarly, int a[5] = {1, 2}; holds 1, 2, 0, 0, 0. A designated initializer, available since C99, chooses a position: int b[5] = {[2] = 7}; puts 7 in the third element and zero in the others.

An automatic local declaration such as int data[5]; reserves space without supplying starting values. Write each element before reading it. Arrays declared outside every function, and arrays declared static, are zero-initialized when no explicit initializer is supplied.

Reach one value by its index

Indexes start at zero. For five elements the valid positions are 0 through 4. scores[1] means the second score; it does not mean the first. An element can be used wherever an ordinary variable of its type can be used:

scores[1] = 75;
scores[1] += 5;
int last = scores[4];

The second score is now 80. The last is still 99. An index can be a variable or an expression, so scores[n - 1] reaches the last element when n is the number of elements and is positive. scores[5] has no element to reach.

Count elements and bytes

sizeof scores measures the entire array in bytes. On a system with 4-byte integers, the five elements occupy 20 bytes. Dividing by one element's size removes the dependence on the integer size:

size_t n = sizeof scores / sizeof scores[0];
printf("%zu\n", n);

This prints 5. size_t is the unsigned integer type used for sizes; %zu prints it. This calculation works where scores names the actual array. A function parameter does not carry the array's full size.

Keep size and values separate

A name such as #define NUM_SCORES 5 lets the declaration and its loops share one size. In C17, a local const int n = 5; int a[n]; instead creates a variable-length array: const does not make n an integer constant expression. Its size is chosen when the declaration runs and then stays fixed for that array's lifetime. It cannot use an initializer list. VLA support is optional in C11 and C17; C23 adds constexpr for compile-time constants.

Arrays cannot be assigned as wholes: b = a; is invalid. Copy their elements with a loop or a suitable memory function. For two separate arrays, a == b compares addresses, not element values. Compare corresponding elements to test value equality. A byte comparison with memcmp instead tests their stored representations; equal floating-point values such as positive and negative zero can have different representations.

Key Takeaways

  • Array indexes run from 0 to one less than the element count.
  • Missing initializer values become zero; uninitialized local elements must be written before use.
  • Use sizeof a / sizeof a[0] only where a is an actual array.
  • An array keeps its size for its lifetime.
  • Copy and compare array values element by element.

1.2 Loop through arrays without going out of bounds

A counting loop connects an array's positions to an operation. Decide which elements hold useful data, then visit exactly those positions. The same pattern supports totals, searches and updates.

Visit the used elements
int t[5] = {12, 15, 9, 14, 10};
int sum = 0;
for (int i = 0; i < 5; i++) {
    sum += t[i];
}
double average = sum / 5.0;

The loop uses indexes 0, 1, 2, 3 and 4. The total progresses through 12, 27, 36, 50 and 60, so the average is 12.0. Writing i <= 5 adds an invalid access at index 5. That extra pass is an off-by-one error.

For an average, make the division floating-point. With a total of 61, 61 / 5 gives the integer 12; 61 / 5.0 gives 12.2. Storing the first result in a double afterward cannot recover the fraction already discarded.

Find a maximum or a match

For a nonempty array, initialize the maximum from its first value:

int max = t[0];
for (int i = 1; i < 5; i++) {
    if (t[i] > max) max = t[i];
}

The first comparison replaces 12 with 15. The remaining values are smaller, so 15 remains. Starting from zero would fail for a list such as {-8, -3, -11}: zero is larger than every stored value, yet it is not in the list.

A linear search checks values in order:

int found = -1;
for (int i = 0; i < 5; i++) {
    if (t[i] == 14) {
        found = i;
        break;
    }
}

Here found becomes 3. The value -1 marks no match because it cannot be a valid index. Report failure only after all candidates have been checked. Returning -1 after the first unequal element misses matches later in the array.

Count and rearrange

A frequency array stores one counter per category. For digits, int counts[10] = {0}; supplies counters 0 through 9. After verifying d >= 0 && d < 10, counts[d]++ increases the counter for digit d. The check protects the array when the digit value comes from input.

To reverse an array, exchange pairs from its ends:

for (int i = 0; i < 5 / 2; i++) {
    int temp = t[i];
    t[i] = t[5 - 1 - i];
    t[5 - 1 - i] = temp;
}

The first swap exchanges 12 and 10. The second exchanges 15 and 14. The middle 9 stays in place, leaving {10, 14, 9, 15, 12}. Stop halfway: continuing through all five positions would swap every pair twice.

Track count separately from capacity

An array with room for 100 values may currently hold only 37 useful entries. Its capacity is 100; its count is 37. Process positions below count. To append one value, first check count < CAPACITY, store at a[count], then increment count. Looping to capacity can read positions that have never received useful values, even though those positions are physically inside the array.

Key Takeaways

  • Use a strict < loop bound.
  • Use floating-point division when an average may have a fraction.
  • Initialize a nonempty array maximum from an element, including for all-negative data.
  • A failed search is decided after all candidates have been checked.
  • Validate a value before using it as an index.
  • Reverse by swapping only the first half of the pairs.
  • Count is the number in use; capacity is the available space.

1.3 Pass arrays to functions

A function can process an array without receiving a separate copy of every element. The call shares access to the caller's storage. Pass the number of elements alongside it so the function knows where to stop.

Supply the data and its length
#include <stddef.h>

double average(const int a[], size_t n) {
    if (n == 0) return 0.0;
    int sum = 0;
    for (size_t i = 0; i < n; i++) {
        sum += a[i];
    }
    return (double) sum / n;
}

The caller can declare int values[] = {7, 8, 10}; and call average(values, sizeof values / sizeof values[0]). The three additions produce 25; converting sum to double before division produces about 8.33. Returning 0.0 for no values is this function's chosen empty-input behavior. The small values in this example keep the integer sum in range.

In a parameter declaration, int a[] is adjusted to int *a. It receives the address of the first element. Even int a[10] does not check or enforce ten elements. The caller must supply a length that matches the available data.

Why sizeof loses the length

At the call site, sizeof values measures the array. Inside the function, sizeof a measures the pointer parameter. Where pointers occupy 8 bytes and integers occupy 4, dividing these sizes gives 2, regardless of the caller's element count. That result describes neither a two-element copy nor the original array. Keep the length as an explicit parameter.

size_t, declared by headers including <stddef.h>, is the unsigned type produced by sizeof. It can represent object sizes, and %zu prints it. Using it for the loop index avoids a signed index being compared with an unsigned length.

Decide whether elements may change

The const in const int a[] prevents assignments to elements through this parameter. It suits a function that computes a result from the data. A function meant to update elements omits it:

void zero(int a[], size_t n) {
    for (size_t i = 0; i < n; i++) {
        a[i] = 0;
    }
}

After zero(values, 3), the caller's three elements all hold zero. The array still has three elements. A scalar parameter such as n is a separate copy; changing that copy would not change a caller's count variable.

To produce multiple results, let the caller provide an output array and its length. A function can fill out[i] with each result. C functions cannot return an array by value, and returning the address of a local array fails because that array stops existing when the function returns.

Count backward with an unsigned index

An unsigned index cannot become negative. In for (size_t i = n - 1; i >= 0; i--), the test is always true; after zero, the value wraps to a large positive number. With n == 0, even the initial subtraction wraps. Start from the count instead:

for (size_t i = n; i > 0; i--) {
    printf("%d\n", a[i - 1]);
}

With three elements the accessed indexes are 2, 1 and 0. With zero elements the body never runs. Include <stdio.h> for printf.

Key Takeaways

  • Pass an array length separately; a parameter has no recoverable element count.
  • Writes through an array parameter change the caller’s elements.
  • Use const for a parameter that only reads elements.
  • The caller supplies storage for multiple output values.
  • Count down from n and access i - 1 when using size_t.
2

Represent text with characters and null-terminated strings

Text begins with character codes and ends with a storage rule: a C string stops at its first zero byte. Learn how characters, literals and writable arrays differ, then use loops to count and transform text. UTF-8 adds an essential distinction between a byte and a complete code point. Following both the text and its storage explains why a visible word can need more array positions than its letters suggest.

2.1 Treat characters as numeric codes

A C character is stored as a numeric code. That lets a program print a symbol, compare it with another symbol, or perform arithmetic on its code. The character set determines which number represents each symbol.

Read the code and the character

A char occupies one byte: sizeof(char) is 1 by definition. A character constant such as 'A' has type int. In the ASCII-based character set used here, its code is 65:

printf("%c %d\n", 'A', 'A');

The output is "A 65". %c displays the character; %d displays its number. ASCII assigns codes 0 through 127. Digits run from 48 to 57, uppercase letters from 65 to 90, lowercase letters from 97 to 122, and space has code 32.

Single quotes and double quotes create different things. 'A' is one character code. "A" is an array containing 'A' and a terminating zero byte. Store one character with char c = 'A'; and compare it using c == 'A'.

Write characters that need escapes
C spellingCharacter
'\n'Newline, ASCII code 10
'\t'Tab, ASCII code 9
'\\'Backslash
'\''Single quote
'\0'Null character, code 0

The null character is different from the visible digit '0', whose ASCII code is 48. A string uses the null character to mark its end.

Turn codes into useful values

The C standard guarantees consecutive digit codes. After checking that c is a digit, c - '0' produces its value. Thus '7' - '0' is 7, not 55. Subtracting the starting code removes the digit's position within the character set.

ASCII letters are consecutive too, so subtracting 'A' converts an uppercase letter to a position from 0 to 25. A Caesar shift by three uses:

char shifted = (c - 'A' + 3) % 26 + 'A';

For 'X', subtracting 65 gives 23; adding three gives 26; the remainder after division by 26 is zero; adding 65 returns 'A'. Plain 'X' + 3 gives code 91, which is '['. Letter arithmetic depends on ASCII-style letter ordering; C guarantees consecutive digits but does not require consecutive letters.

Classify and convert with ctype.h

Include <ctype.h> for isdigit, isalpha, isalnum, isspace, isupper, islower, toupper and tolower. The tests return nonzero for a match and zero otherwise. toupper('q') returns 'Q'; toupper('3') leaves the digit unchanged. isspace recognizes separators such as space, tab and newline.

Plain char may be signed. A byte above 127 can then become a negative value, which is not a valid argument for these functions unless it is the special input marker EOF. When processing bytes from a string, convert first:

if (isalpha((unsigned char) c)) {
    c = (char) toupper((unsigned char) c);
}

In the default "C" locale these functions recognize the basic letters, not whole multibyte UTF-8 characters. The cast makes the byte a valid argument; it does not decode UTF-8.

Key Takeaways

  • Character constants use single quotes; string literals use double quotes.
  • \0 is a zero-valued character, not the digit 0.
  • Subtract '0' from a digit character to obtain its value.
  • Letter-position arithmetic here uses ASCII ordering.
  • Convert a string byte to unsigned char before a ctype call.
  • A ctype test returns nonzero for a match, not necessarily 1.

2.2 Store text as null-terminated strings

A C string is a sequence of characters ending with '\0'. The terminator is part of its storage but is not part of its text length. C has no separate built-in string type: the characters occupy an array.

See the bytes in the array
char word[] = "cat";
char name[20] = "Ada";

word contains four bytes: 'c', 'a', 't', '\0'. Its array size is 4 and strlen(word) is 3. Include <string.h> for strlen. The larger name array contains the three letters, their terminator and sixteen more zero bytes. Its capacity is 20 while its current length is 3.

A character-by-character initializer can make the same string:

char word[] = {'c', 'a', 't', '\0'};

Leaving off the last element creates three characters without a string terminator. C also permits char word[3] = "cat";, which fills all three positions and omits the zero byte. Such an array is not a C string. A function such as strlen or printf with %s keeps reading until it encounters a zero byte, so it cannot safely consume that three-byte array as text.

Choose a modifiable array or a literal
DeclarationWhere the text isCan its characters change?
char a[] = "hello";A copy in array aYes
const char *p = "hello";A string literal referred to by pNo

a[0] = 'H'; changes the copy to "Hello". A literal must not be modified. Even if it is assigned to a plain char *, writing through that pointer has undefined behavior. Writing const char * makes that restriction visible to the compiler.

The compiler joins adjacent string literals: "Hello, " "world" gives the same text as "Hello, world". The resulting string still has one final terminator. This is useful for splitting a long literal over source lines.

Print and change the text
printf("%s\n", word);
printf("%c\n", word[0]);

The first call prints the whole string. The second prints its first character. A string literal in printf is surrounded by double quotes because it is text containing a format; %c receives one character code.

An element assignment changes one stored character. With char name[20] = "Ada";, setting name[0] = 'E'; produces "Eda". Setting name[1] = '\0'; afterward produces "E" as the string. The bytes for 'a' and the old terminator still exist later in the array, but string operations stop at the first zero.

An array cannot receive replacement text using name = "Bob"; after declaration. Copy characters into it, using a checked strcpy call or a loop, and include the terminator. The replacement's length must be less than the capacity.

Represent no text

The empty string "" still occupies one byte for '\0'. It has length zero and can be tested with name[0] == '\0'. An empty string is a real string with a terminator; a null pointer instead refers to no string storage.

Key Takeaways

  • A C string ends at its first '\0'.
  • Capacity includes the terminator; string length excludes it.
  • A character array without a terminator cannot be passed as a C string.
  • Array copies of literals are modifiable; literals themselves are not.
  • Writing an earlier terminator shortens text without resizing its array.

2.3 Process strings one character at a time

A string's terminator gives a loop its stopping point. Visit the bytes before it to count, convert or filter text. When creating a new string, the program must write a new terminator itself.

Walk to the end once
for (size_t i = 0; s[i] != '\0'; i++) {
    /* Process s[i]. */
}

At every pass, the test asks whether the current byte belongs to the text. It stops before the terminator. Calling strlen(s) in that test can scan the whole string again on each pass. Either test the current byte directly or compute size_t len = strlen(s); once when the length will stay unchanged.

For uppercase conversion, use a modifiable array and <ctype.h>:

for (size_t i = 0; s[i] != '\0'; i++) {
    s[i] = (char) toupper((unsigned char) s[i]);
}

"Hello, World 42" becomes "HELLO, WORLD 42". Conversion preserves the comma, spaces and digits. The unsigned conversion makes every stored byte a valid ctype argument.

Count categories and word starts

The original "Hello, World 42" has 15 text bytes: 10 letters, two digits, two spaces and one comma. Separate counters can use isalpha, isdigit and isspace. Decide what a word means before counting it. For a run of letters, a new word starts when a letter follows a non-letter or is the first character:

int in_word = 0;
size_t words = 0;
for (size_t i = 0; s[i] != '\0'; i++) {
    int letter = isalpha((unsigned char) s[i]) != 0;
    if (letter && !in_word) words++;
    in_word = letter;
}

The example has two letter-runs, "Hello" and "World". The digits do not form a word under this definition. Updating in_word every pass lets punctuation end a run.

Reverse only the text

For a single-byte string, obtain len once, then swap s[i] with s[len - 1 - i] while i < len / 2. Reversing "stressed" exchanges its first and last letters, then the next pair, until it becomes "desserts". The zero byte stays at index len. Starting a swap at s[len] would move that zero to the front. Reversing bytes this way is appropriate for ASCII text; multibyte characters need decoding before rearrangement.

Build a filtered result

Use different indexes for reading and writing, because some input bytes are skipped:

char in[] = "a1b2c3";
char out[sizeof in];
size_t j = 0;
for (size_t i = 0; in[i] != '\0'; i++) {
    if (isalpha((unsigned char) in[i])) {
        out[j++] = in[i];
    }
}
out[j] = '\0';

The result is "abc". The destination has enough room because filtering cannot lengthen this input. Without out[j] = '\0';, the output has no defined string ending.

For a Caesar cipher, shift uppercase letters relative to 'A' and lowercase letters relative to 'a', then copy other bytes unchanged. With key 13, "Hello, World!" becomes "Uryyb, Jbeyq!". Repeating that shift restores the original because two shifts total 26. A comparison such as s[i] == 'a' tests one character; use strcmp to compare complete text.

Key Takeaways

  • Test the current byte or cache the length to avoid repeated full scans.
  • Keep separate read and write indexes when filtering.
  • Write a terminator after building a new string.
  • Reverse text bytes without moving the terminator.
  • Define a word boundary before writing a word counter.
  • Apply ASCII letter shifts separately to uppercase and lowercase letters.

2.4 Explain how UTF-8 text is stored in char arrays

Text length can mean a byte count or a count of Unicode code points. These counts often differ. A C char array exposes individual bytes, so its indexes and string functions do not automatically follow complete non-ASCII characters.

Separate characters, code points and bytes

A character encoding describes how text is stored. ASCII uses codes 0 through 127 for basic letters, digits, punctuation and control characters. Unicode assigns code points to characters from many writing systems: "é" has code point U+00E9 and "€" has U+20AC. UTF-8 encodes Unicode scalar values using one to four bytes.

ASCII code points retain their one-byte encodings. The precomposed "é" uses two bytes, 0xC3 and 0xA9; "€" uses three. Many individual emoji code points use four bytes. A displayed symbol can contain multiple code points, so even a code-point count is not always a count of visible symbols.

Measure a UTF-8 string
char s[] = "café";
printf("%zu %zu\n", strlen(s), sizeof s);

With UTF-8 execution text, this prints "5 6". The first three letters use three bytes and "é" uses two, making five text bytes. The terminator adds the sixth byte of storage. s[3] is the first byte of "é", not the entire letter.

A cut after four bytes would retain 0xC3 but remove its continuation byte 0xA9. The result would no longer be valid UTF-8. A buffer limit expressed in bytes therefore needs a character-boundary check if the program truncates UTF-8 text.

Count starting bytes in valid UTF-8

A continuation byte starts with the bits 10. Its value lies from 0x80 through 0xBF. A leading byte or ASCII byte does not have that pattern:

size_t points = 0;
for (size_t i = 0; s[i] != '\0'; i++) {
    unsigned char c = (unsigned char) s[i];
    if ((c & 0xC0) != 0x80) points++;
}

The mask 0xC0 keeps just the top two bits; 0x80 represents the pattern 10 in those positions. The test counts "café" as four code points by counting its four starting bytes. It assumes valid UTF-8 and does not validate malformed sequences or combine code points into visible symbols.

Choose operations that preserve the encoding

Copying a complete UTF-8 string preserves its bytes. Joining complete strings, comparing their byte sequences for equality, and searching for an ASCII character or complete UTF-8 substring also work. UTF-8 continuation bytes cannot be mistaken for ASCII characters or leading bytes.

strcmp orders unsigned byte values, so its order differs from language-specific alphabetical order. In UTF-8, ordinary accented Latin letters have leading bytes above ASCII 'z'. A byte comparison therefore puts those letters after 'z', regardless of dictionary conventions.

In the default "C" locale, toupper and isalpha process one byte and do not decode "é" into a letter. Casting to unsigned char makes the call valid but cannot make it a Unicode case conversion. Wide-character functions or a Unicode library are needed for that task. An ASCII-only Caesar loop can preserve accented letters by changing only bytes from A–Z or a–z.

The zero byte is UTF-8's encoding of U+0000; other encoded code points contain no zero byte. That fits C's terminator convention. UTF-16 can put zero bytes inside ordinary encoded text, so a UTF-16 byte array cannot be processed as an ordinary char string.

Key Takeaways

  • strlen counts bytes, including in UTF-8 text.
  • One Unicode code point may occupy several bytes.
  • A char index can land inside an encoded character.
  • Counting non-continuation bytes counts code points only in valid UTF-8.
  • Bytewise equality works for identical encoded text; byte order is not dictionary order.
  • Single-byte ctype calls do not decode UTF-8.
3

Use the standard string functions

The standard library provides common text operations, but each function expects a particular kind of input and a particular meaning for its size argument. Learn to measure, copy, append, compare and search strings, then choose bounded formatting or byte operations where they fit. Checking capacity and interpreting return values turns a short function call into a predictable operation on known storage.

3.1 Measure, copy and join strings

The functions in <string.h> work with terminated text. They can find its length, copy it or append more text, but copying functions do not discover the destination's capacity. Work out the required storage before writing.

Measure length without the terminator

strlen(s) returns a size_t giving the number of bytes before the first zero byte. Thus strlen("hello") is 5 and strlen("") is zero. Print the result with %zu.

char buf[64] = "hi";
printf("%zu %zu\n", strlen(buf), sizeof buf);

This prints "2 64". The first result measures current text; the second measures available array storage. A later copy can change the first without changing the second. Both calls are useful here because buf is an actual array and already contains a valid string.

Copy a complete string

strcpy(dest, src) copies text and its final zero byte. The required capacity is the source length plus one:

char dest[12];
const char *src = "Maple";
if (strlen(src) < sizeof dest) {
    strcpy(dest, src);
}

"Maple" uses five text bytes and one terminator, so it fits. The strict < matters: a source whose length equals the capacity leaves no position for its zero byte. On a failed check the program must handle the error before using the destination as a new string.

An array assignment such as dest = src; cannot replace its contents. Assigning one char * variable to another is legal, but only copies a reference to the same storage. It does not create another copy of the characters.

Append to an existing string

strcat(dest, src) first finds the terminator already in dest. It replaces that terminator with the first source byte and writes a new terminator after the appended text. Destination capacity must cover both current lengths plus one.

char full[16];
strcpy(full, "Ada");
strcat(full, " ");
strcat(full, "Lovelace");

The lengths grow from 3 to 4 to 12. The final "Ada Lovelace" needs 13 bytes of storage, leaving three bytes unused. A 12-byte destination would be one byte too small, despite matching the visible text length.

Never append to an uninitialized local buffer. char out[20]; strcat(out, "hi"); gives strcat no established text ending to find. Start with char out[20] = "";, write out[0] = '\0';, or make the first operation a checked copy.

Respect storage and scan costs

The source and destination must not overlap for strcpy or strcat. Moving text within the same buffer can create an overlap even when both locations are individually valid. memmove is the function intended for overlapping byte regions.

strlen must scan up to the terminator. strcat must scan the current destination before it appends. Repeated appends in a loop therefore rescan text that has already been built. When constructing many pieces, keep the current output length and write each piece at that index, checking the remaining capacity and maintaining the terminator. A known length is useful only while it still matches the actual contents.

Key Takeaways

  • strlen measures current text; sizeof an array measures its capacity.
  • A copy needs source length plus one byte.
  • An append needs both string lengths plus one byte.
  • Initialize the destination as a string before appending.
  • Copying or appending with overlapping string regions is invalid.

3.2 Compare and search strings

String comparison answers questions about text, while string search finds a place inside that text. Neither task is performed by comparing array names with ==. Use the library result according to what it represents: a comparison sign, a length or a matching location.

Compare text and prefixes

strcmp(a, b) compares corresponding bytes as unsigned char values until a difference or the string ending. Its result is zero for equal text, negative when a comes first and positive when it comes later. Only the sign is specified; do not expect exactly -1 or 1.

if (strcmp(command, "quit") == 0) {
    puts("Goodbye");
}

The explicit zero test means equal. A plain if (strcmp(command, "quit")) enters for unequal text because nonzero is true. Comparing command == "quit" would compare storage locations.

In ASCII order, "apple" comes before "banana" at the first letter. "Zebra" comes before "apple" because uppercase 'Z' has code 90 and lowercase 'a' has code 97. "app" comes before "apple" because its zero byte is encountered before the other's 'l'. This is byte ordering, not a dictionary comparison.

strncmp(a, b, n) compares at most n bytes. strncmp(s, "--", 2) == 0 recognizes a two-dash prefix but does not mean that the entire string is just "--". Standard C has no case-insensitive whole-string comparison function. A program can compare converted bytes itself; POSIX provides strcasecmp in <strings.h>.

Search for a location

Use strchr for the first occurrence of one character, strrchr for the last and strstr for the first complete substring. They return a pointer to the matching location or NULL if there is no match.

char name[] = "report.final.txt";
char *extension = strrchr(name, '.');
if (extension != NULL) {
    printf("%s\n", extension);
}

This prints ".txt", including the matched dot. Starting from the first dot with strchr would print ".final.txt". strstr(name, "final") finds the suffix beginning "final.txt"; searching for "draft" gives NULL. Check for NULL before printing or reading a search result.

Locate a separator or split text

strcspn(s, set) counts the initial bytes that belong to none of the characters in set. With "oak:pine,birch" and the set ":,", it stops at the colon. For a valid input string, this pattern removes the first newline:

buf[strcspn(buf, "\n")] = '\0';

If there is no newline, the returned index is the string length, so the assignment rewrites the terminator already there.

strtok splits a modifiable string using any character in a delimiter set:

char colors[] = "red, green,blue";
char *token = strtok(colors, " ,");
while (token != NULL) {
    puts(token);
    token = strtok(NULL, " ,");
}

The tokens are "red", "green" and "blue". The first call supplies the array; later calls pass NULL to continue. The function writes zero bytes into the input and keeps hidden state between calls. Use writable storage, and do not expect the original separator-filled string to remain intact. Consecutive delimiters do not produce empty tokens.

Key Takeaways

  • Use strcmp(a, b) == 0 for equal text.
  • Interpret a comparison result by sign, not exact magnitude.
  • A prefix match does not establish whole-string equality.
  • Search results need a null-pointer check.
  • strcspn returns a prefix length; strtok modifies its input and remembers its position.
  • String ordering follows unsigned byte codes.

3.3 Copy within limits and work with raw memory

A length limit is useful only when it describes the right quantity. Some functions limit source bytes, others destination capacity, and memory functions handle raw bytes without string endings. Read each function's contract before choosing its bound.

Know what n means

strncpy(dest, src, n) writes exactly n positions when the source is shorter, padding with zero bytes. When the source has at least n bytes of text, it copies those bytes without a terminator. For a positive-capacity array, a terminated truncating copy is:

strncpy(dest, src, sizeof dest - 1);
dest[sizeof dest - 1] = '\0';

The final assignment is essential when the source fills the limit. Truncating a name or command can still change its meaning, so reject overlong input when a complete copy is required.

strncat(dest, src, n) appends at most n text bytes and then a terminator. It does not receive total capacity. For a valid string already inside a destination array, the remaining text space is sizeof dest - strlen(dest) - 1. Passing sizeof dest as n can overflow it.

Format and detect truncation

snprintf, declared in <stdio.h>, takes total destination capacity. With capacity greater than zero it writes at most one fewer text bytes, then a terminator:

char buf[8];
int needed = snprintf(buf, sizeof buf, "%s-%d", "item", 2026);
if (needed < 0) {
    puts("Formatting error");
} else if ((size_t) needed >= sizeof buf) {
    puts("Text did not fit");
}

The full result "item-2026" has nine text bytes. The buffer stores "item-20" and its zero byte, while needed is 9. The return value counts the text that would have been produced, excluding the terminator. Check a negative result before converting it to size_t.

Copy and fill bytes

memcpy(dest, src, n) copies exactly n bytes, including any zero bytes, without searching for a terminator. For two same-size integer arrays, memcpy(b, a, sizeof a) copies their complete storage when the regions do not overlap. Where each of five integers occupies four bytes, that count is 20 bytes. memmove performs byte copying with defined behavior for overlapping regions.

memset(a, 0, sizeof a) fills every byte with zero and zeroes an integer array. Its second argument describes each byte, not each element. With four-byte integers, filling with 1 makes the bytes 01 01 01 01, giving 16843009 on the ordinary representation, not the integer 1. Use a loop to assign nonzero integer values.

memcmp(a, b, n) compares stored bytes. Zero means those byte sequences match. For value equality, compare elements: floating-point 0.0 and -0.0 compare equal while their representations can differ.

Recognize other library families

BSD's strlcpy and strlcat joined POSIX in 2024, but are not core C17 functions. They return a length they tried to create; a result at least as large as the supplied size signals truncation. strlcpy terminates when the size is positive. strlcat terminates only if the existing text ends within that size. They are available in glibc 2.38 and later. Optional Annex K functions such as strcpy_s are absent from many libraries, including glibc.

Key Takeaways

  • strncpy may omit the terminator; strncat needs space for a new one.
  • snprintf returns the complete required text length, or a negative error.
  • A nonnegative formatting result at least as large as capacity means truncation.
  • Memory functions count bytes and do not stop at zero.
  • Use memmove for overlapping regions.
  • memset fills bytes; memcmp compares representations.
4

Organize data in multidimensional arrays

Tables organize values by more than one position. A C table stores equal-width rows in sequence, and nested loops let a program work by row or by column. Learn how to declare that shape, pass it to a function, and keep both indexes valid. Lists of text introduce a useful design choice: writable character rows reserve fixed space, while arrays of pointers refer to strings held elsewhere.

4.1 Declare and traverse two-dimensional arrays

A table needs a row index and a column index. In C, a two-dimensional array is an array whose elements are themselves arrays. Each row has the same number of elements, and each index has its own bound.

Declare rows and columns
int grid[3][4];
int m[2][3] = {{1, 2, 3}, {4, 5, 6}};

grid holds three rows with four integers each: twelve integers in total. Its row indexes are 0 through 2; its column indexes are 0 through 3. grid[1][2] reaches the third value in the second row.

Nested initializer braces show the row grouping. Missing initialized elements become zero. You can let the compiler count rows in int m[][3] = {{1, 2, 3}, {4, 5, 6}};, but the column count must be supplied. Without it the compiler could not determine where each row ends.

Follow row-major storage

C stores a complete row before starting the next. In grid, the first four stored integers are row 0, the next four are row 1 and the last four are row 2. grid[1][2] is the seventh integer from the start, at zero-based element position 6: one complete row of four, then two more elements.

Where integers occupy four bytes, one row uses sixteen bytes and the whole table uses forty-eight. At the declaration site:

size_t rows = sizeof grid / sizeof grid[0];
size_t cols = sizeof grid[0] / sizeof grid[0][0];

The first division gives 3 and the second gives 4. A row is an array in its own right, which is why sizeof grid[0] measures more than one integer.

Traverse and total the table
int sales[2][3] = {{4, 0, 7}, {5, 2, 1}};
int total = 0;
for (int r = 0; r < 2; r++) {
    int row_sum = 0;
    for (int c = 0; c < 3; c++) {
        row_sum += sales[r][c];
    }
    printf("%d\n", row_sum);
    total += row_sum;
}

The first row produces 11 and the second 8. Adding those row totals gives 19. Resetting row_sum inside the outer loop starts a fresh total for each row. If it were initialized only once before both loops, the second printed value would include the first row too.

For column totals, put the column loop outside and the row loop inside, still accessing sales[r][c]. The three totals become 9, 2 and 8, which again add to 19. Changing traversal order does not require changing the meaning of the two indexes. Row-first traversal follows contiguous storage and often improves cache use for large tables.

Keep each dimension within bounds

grid[c][r] swaps the meanings of the indexes. If c reaches 3, it requests a fourth row that does not exist. The fact that the complete object holds twelve integers does not permit crossing one row's bound with a column index.

Write two separate indexing operations: grid[1][2]. In C17, grid[1, 2] evaluates the comma expression and selects grid[2], a row, instead of one element. More dimensions apply the same idea repeatedly: int cube[2][3][4] has 24 integer elements, selected by three indexes.

Key Takeaways

  • A two-dimensional array is an array of equal-width rows.
  • Rows are stored one after another in row-major order.
  • Keep row and column bounds separate.
  • Reset a row or column accumulator when starting a new group.
  • Only the first dimension may be inferred from an initializer.

4.2 Pass tables to functions and store lists of strings

A function receiving a table needs enough information to identify each row. A list of strings adds another choice: store the characters in equal-width rows, or store references to strings whose lengths differ.

Give a table parameter its row width
void double_grid(int rows, int g[][4]) {
    for (int r = 0; r < rows; r++) {
        for (int c = 0; c < 4; c++) {
            g[r][c] *= 2;
        }
    }
}

The first dimension can be omitted because the row count is passed separately. The width 4 defines the type of each row. Without it, g[r][c] would have no known row spacing. A declaration with int g[][] is invalid. Changes made through this parameter remain in the caller's table; a read-only table parameter can use const int g[][4].

Since C99, compilers supporting variably modified parameters accept dimensions declared earlier in the parameter list:

void print_grid(int rows, int cols, int g[rows][cols]);

GCC and Clang support this C17 form. It allows one function to receive different row widths; pass positive dimensions that match the actual table. C23 requires support for these parameter types, while automatic variable-length array objects remain a separate optional feature. Fixed-width parameters are useful when compiler support is limited.

Store strings in rows
char days[7][10] = {
    "Monday", "Tuesday", "Wednesday", "Thursday",
    "Friday", "Saturday", "Sunday"
};

Each row has ten characters of storage, regardless of its current text length. "Wednesday" needs all ten positions: nine letters and a terminator. days[2] selects that whole row as a string; days[2][0] selects 'W'.

A row can be printed with %s, measured with strlen, or compared with strcmp. It can also receive a checked copy. strcpy(days[0], "Mon"); fits in ten bytes and changes row 0's text. The row stays ten bytes wide, and the next row stays in the same location. A replacement of ten text bytes would need eleven bytes and would not fit.

Store references to literals
const char *names[] = {"Ann", "Bartholomew", "Li"};

This declares an array of pointers. Each element refers to a literal with its own length plus a terminator. names[1] refers to "Bartholomew", and names[1][0] is 'B'. The literals must not be modified.

The pointer elements can change even though the literal characters cannot. names[0] = "Anna"; changes which literal the first element refers to; it does not extend "Ann" in place. In the 2D row form, an element is a fixed-width character array and cannot be reassigned that way. Choose writable rows for text edited in place, and references to literals for a list of fixed text choices. The command-line parameter argv uses an array-of-pointers shape, although its supplied argument characters are modifiable.

Key Takeaways

  • Table parameters need every dimension after the first.
  • A dimension passed separately must match the actual table.
  • Each string row needs its own terminator within its fixed width.
  • An array of pointers stores references, not equal-width character rows.
5

Process command-line arguments

A command line supplies a program with text at startup. Argument counts, shell grouping and string operations let the program interpret that text without assuming an argument exists or already contains a number. Follow a checked path from argv to a usable value: verify presence, convert completely, detect conversion errors and enforce the range the program actually accepts.

5.1 Read argc and argv

Command-line arguments let the caller supply text when starting a program. The operating environment makes those strings available to main. Check how many arrived before indexing them.

Read the argument list
int main(int argc, char *argv[]) {
    for (int i = 1; i < argc; i++) {
        printf("%d: %s\n", i, argv[i]);
    }
    return 0;
}

Include <stdio.h> for the output. argc is the argument count and argv is the list of strings. The equivalent parameter spelling char **argv describes the same interface. The loop starts at 1 because index 0 normally holds the program's launch name.

On an ordinary shell, running ./greet Ada Lovelace supplies three strings: "./greet", "Ada" and "Lovelace". Thus argc is 3 and the last user argument is argv[2]. A null pointer sits at argv[argc], here argv[3]; it is a sentinel after the actual argument strings.

Let the shell group text

Quotes affect how the shell builds arguments. Running ./greet "Ada Lovelace" supplies one user argument containing a space. Its quote marks group the words and do not become part of the argument text. argc is now 2.

Unix-like shells can also expand patterns before the program starts. In ./count *.txt, the program commonly receives the names matched by the shell as separate arguments. It receives the resulting strings, not a request to expand the pattern itself. Exact expansion behavior depends on the shell and its settings.

Reject a missing or extra argument

If a program needs exactly one user argument, require argc == 2 before using it:

if (argc != 2) {
    printf("Usage: %s name\n", argv[0]);
    return 1;
}
printf("Hello, %s\n", argv[1]);

When argc is 1, argv[1] is the sentinel null pointer and cannot be passed to strlen or printed with %s. Indexes beyond the sentinel are outside the argument array.

Returning zero from main signals success. A nonzero status, such as the 1 above, tells the shell the program did not complete successfully. <stdlib.h> also supplies EXIT_SUCCESS and EXIT_FAILURE.

Treat each argument as a string

argv[i][0] reaches the argument's first character; strlen(argv[i]) measures its text bytes; strcmp(argv[i], "-v") == 0 checks a complete option. A numeric-looking argument is still text and must be converted before arithmetic.

argv[0] usually records how the program was started, but it may be empty when that information is unavailable. It is useful for messages, not as a fixed identity check. Argument characters may be modified during the program, but their supplied storage gives no room to assume a longer replacement will fit. To append an extension or otherwise lengthen text, copy it into a separately sized buffer first.

Key Takeaways

  • argc counts the supplied argument strings, including the normal launch-name entry.
  • Check the argument count before reading a user argument.
  • argv[argc] is a null pointer, not an empty argument string.
  • Shell quotes can keep spaces inside one argument.
  • Use string operations for argument text and conversion for numbers.

5.2 Convert and validate numeric arguments

A numeric argument arrives as characters, not as an integer. Reliable conversion checks three separate questions: did any digits form a number, did the conversion use the required text, and is the resulting value allowed?

Avoid losing the error information

atoi, declared in <stdlib.h>, performs a simple conversion. It turns "13" into 13 but also turns "12abc" into 12. It returns zero both for "0" and for text such as "abc" that begins with no number. An out-of-range result is undefined behavior, so a digits-only test followed by atoi is still unsafe for very long input.

A form rule such as “one or more digits only” needs a separate scan. Reject an empty string and require each byte to satisfy isdigit((unsigned char) c). That rule accepts "13" and rejects "13a", "-5" and "". It checks spelling, not whether the value fits in a machine integer.

Use strtol and inspect where it stopped
#include <errno.h>
#include <stdlib.h>

char *end;
errno = 0;
long count = strtol(argv[1], &end, 10);
if (end == argv[1] || *end != '\0' || errno == ERANGE) {
    puts("Enter a whole number");
    return 1;
}
if (count < 1 || count > 100) {
    puts("Count must be from 1 to 100");
    return 1;
}

This fragment follows an argument-count check and uses <stdio.h> for puts. The 10 selects decimal conversion. end receives the location where conversion stopped; &end lets the function store that location. *end reads the character there. No pointer arithmetic is needed.

For "13a", the returned value is 13 but *end is 'a', so the text is rejected. For an empty string or "abc", end equals the starting argument, showing that no number was consumed. Both checks are needed: testing only the ending zero would mistakenly accept an empty string.

Before every conversion, reset errno to zero. A value outside the long range makes strtol return an endpoint and set ERANGE. Without the reset, an old error could appear to belong to this conversion. Check before narrowing the result to int; use INT_MIN and INT_MAX from <limits.h> when the full int range is the accepted domain.

Apply the program's own rule

strtol accepts leading whitespace and an optional sign. Decide whether those forms are allowed. If the interface requires digits alone, perform the nonempty digit scan as well as the conversion checks. After a successful conversion, check the application range before using a value as a count or index. "0" can be perfectly valid numeric text and still be invalid for a count that must be positive.

A Caesar program accepting a nonnegative key first checks argc, then its digits-only form, then the complete strtol result and range. Reducing a valid key with % 26 produces a shift from 0 through 25 before letter arithmetic. Key 27 has the effect of key 1. Perform the range check before a narrowing cast or any arithmetic that might overflow. For decimal values, strtod provides the corresponding end-position and range-error interface.

Key Takeaways

  • Numeric-looking arguments still need conversion.
  • atoi cannot distinguish valid zero from a conversion failure.
  • Check that strtol read something and consumed all required text.
  • Reset errno, check ERANGE, then check the application range.
  • A digits-only scan does not rule out overflow.
  • Validate before narrowing a value or using it as an index.
6

Prevent out-of-bounds access and buffer overflows

Bounds mistakes connect several earlier ideas: a loop can take one extra pass, text can need one extra byte, and a function can lose track of an array’s capacity. Learn to identify the invalid access before predicting its effects. Then read and copy input within known limits, checking success and completeness separately so a buffer that fits still contains the data the program intended to accept.

6.1 Recognize out-of-bounds access and its effects

Every array access needs a valid index. C does not require a bounds check at runtime, so the programmer must keep the index inside the object. An access outside its range has undefined behavior: the language gives no promised result.

Recognize an invalid access

For int a[5], valid indexes are 0 through 4. Index 5 is already too large and -1 is negative. A compiler may diagnose a visible mistake, but many invalid indexes depend on runtime input and are not rejected during compilation.

A faulty program may appear to work, crash or produce unexpected values. A write can corrupt nearby state; the symptom may occur later in an unrelated calculation. A read can access bytes outside the intended object. Neither operation has a reliable result. Repeating a successful run cannot establish that the access is valid.

Count every write, including the terminator
char name[8];
strcpy(name, "Christopher");

"Christopher" has eleven text bytes. Copying it also writes the terminator, so this call attempts twelve bytes in eight positions. That is a buffer overflow. On real systems, an overflow can damage other data or control information. Historically, attacks including the 1988 Morris worm exploited such input-handling bugs.

An overflow can be just one byte:

char s[5];
for (int i = 0; i <= 5; i++) {
    s[i] = 'x';
}

The body runs six times and writes index 5. Changing the test to i < 5 keeps five character writes within the array, but it still does not create a string. For a string of four 'x' characters, write only indexes 0 through 3 and then store '\0' at index 4.

Validate computed indexes

A frequency table can turn a character into an index, but subtraction alone supplies no range guarantee:

int counts[26] = {0};
if (c >= 'a' && c <= 'z') {
    counts[c - 'a']++;
}

With ASCII ordering, that test limits the result to 0 through 25. For 'A' or a punctuation byte, using c - 'a' without the test could create a negative index. Test the original value before indexing; checking after the write is too late.

For a table, validate the row and column separately. In int g[2][3], a column index of 3 is invalid even if the next row is adjacent in memory. Do not use the complete table's storage as permission to exceed a row's bound.

Use checks that fit the operation

An array loop uses a strict upper bound; an append checks count against capacity; a string copy checks text length plus one. Functions receive actual lengths or capacities from their callers. These checks address different quantities, so one hardcoded size cannot replace them all.

Compile with warnings such as -Wall -Wextra. GCC and Clang's -fsanitize=address -g can report many invalid accesses on executed paths and show the source location. Such testing helps find mistakes, but unexecuted paths still require reasoning about bounds. Review each indexed read and write by identifying its object, its bound and the condition that keeps that index valid.

Key Takeaways

  • Out-of-bounds reads and writes have undefined behavior.
  • A successful run is not evidence that an index is valid.
  • A copied string includes its terminator in the write count.
  • Validate data before deriving an array index.
  • Check each table dimension independently.
  • Warnings and sanitizers help detect bugs on tested paths.

6.2 Read and copy text within buffer limits

Input length is controlled by whoever supplies the input. Give reading functions a real buffer limit and check what they return. Then decide whether the data forms a complete line and whether it fits the program's own rules.

Read one word with a width

For char word[16];, scanf("%15s", word) reads at most fifteen non-whitespace characters and appends '\0'. The width leaves one of the sixteen positions for the terminator. %s without a width has no length limit; gets had the same fundamental problem for lines and was removed from C in C11.

Check that scanf returns 1 before using a word. It reads a word, not a whole line with spaces. Excess word bytes can remain unread, so a width protects storage without deciding whether the whole input is acceptable.

Read a line with fgets
char buf[8];
if (fgets(buf, sizeof buf, stdin) == NULL) {
    return 1;
}

fgets reads at most seven characters here and writes a final zero byte. It keeps a newline when one is reached before the limit. A line containing "hello" and a newline becomes "hello\n" plus the terminator. A final line ending at end-of-file without a newline can still be a successful read. If no characters were read before end-of-file, or a read error occurred, the result is NULL; do not process that buffer as new input.

Distinguish a stored newline from a partial line

Find a newline before removing it:

size_t end = strcspn(buf, "\n");
int had_newline = buf[end] == '\n';
buf[end] = '\0';

Include <string.h>. If no newline was stored, end is the text length and the assignment rewrites the existing terminator. A missing newline can mean a partial line, an exact buffer fill before a newline, or a final line that ended at end-of-file. It alone cannot establish that more characters remain.

For the input line "abcdefghij" and an eight-byte buffer, the first read stores "abcdefg" and the next can read "hij\n". Treating the second chunk as a separate answer would split one logical input. A program can reject a line missing a required newline, or read and discard its remainder before asking again. State the input policy before implementing it.

For up to thirty ASCII name characters plus a newline and a terminator, use 32 bytes. For UTF-8, that is a byte limit: thirty displayed characters may need more storage.

Copy only after checking all requirements
char user[10];
if (argc != 2) return 1;
if (strlen(argv[1]) >= sizeof user) {
    puts("Name too long");
    return 1;
}
strcpy(user, argv[1]);

The argument-count check comes first. The length check then admits at most nine text bytes, leaving the tenth position for zero. A writing function should receive capacity explicitly, such as read_name(out, capacity), because sizeof out inside it measures a pointer parameter.

For numeric input, read a complete line and convert with strtol, checking the end position, range error and allowed value range. A failed scanf("%d", &n) can leave the offending text unread; blindly repeating it can fail forever on the same bytes. At minimum check its conversion count and handle the invalid input before retrying.

Key Takeaways

  • Set a word-read width to one less than buffer capacity.
  • Check a read result before using its buffer.
  • fgets retains a newline only when it reaches one within the limit.
  • A missing newline does not by itself establish that input remains.
  • Budget for text bytes, the newline when retained, and the terminator.
  • Check argument presence before measuring and copying its text.
  • Pass buffer capacity into a function that writes to it.

Arrays and Strings in C examples

6 Walkthroughs

1

Follow an array from declaration to function call

13 steps
Trace initialized elements, compute an average and share array data with a function.
  1. 1
    IntroductionGive several values one name

    An array holds a fixed number of elements of one type. A loop selects positions, while a function can work on the same storage through an array parameter.

    Follow four sensor readings from their declaration through a total and an average. At each step, keep three quantities distinct: the number of elements, the last valid index and the number of bytes used for storage.

  2. 2
    ExampleRead the initial positions
    int readings[4] = {11, 16, 13};
    

    There are four elements, with indexes 0 through 3. The initializer supplies 11, 16 and 13 at the first three positions. The omitted value becomes zero, so the array is {11, 16, 13, 0}.

    The zero is an initialization rule, not a sign that the array has only three elements. A local array with no initializer would need its values written before use.

  3. 3
    ExplanationCount elements at the declaration
    size_t n = sizeof readings / sizeof readings[0];
    

    The numerator measures all four elements and the denominator measures one. Where int occupies four bytes, this is sixteen divided by four, giving 4. The same expression works with another integer size.

    readings[3] is the last element. readings[4] is past the end even though the array's count is 4. size_t is the unsigned type used for this size result.

  4. 4
    ChallengeChange the last reading

    The declaration is still int readings[4] = {11, 16, 13};. What assignment replaces the zero with 20, and what count results?

    ---reveal---

    Use readings[3] = 20;. The resulting values are {11, 16, 13, 20} and the count stays 4. An element assignment changes a value, not the number of positions. Index 4 would be one position too far and cannot add storage to this array.

  5. 5
    ExampleBuild the total one pass at a time
    int sum = 0;
    for (size_t i = 0; i < n; i++) {
        sum += readings[i];
    }
    

    Using {11, 16, 13, 20}, the running sum becomes 11, 27, 40 and 60. After index 3, i becomes 4 and the test fails before another access.

    Starting sum at zero gives the first value a clean starting point. The strict upper bound lets the loop visit exactly the four existing elements.

  6. 6
    Key InsightPreserve a fractional average

    For these four readings, (double) sum / n gives 15.0. The cast happens before division, so the operation can produce a fractional result when the total changes.

    If the total were 61, integer division sum / 4 would produce 15, while sum / 4.0 would produce 15.25. Assigning the already truncated integer result to a double would only produce 15.0; it would not recover the discarded fraction.

  7. 7
    TipChoose a valid starting candidate

    A maximum search needs a starting value drawn from a nonempty array. For {-6, -2, -9}, initialize max to -6, then replace it with -2 when that larger value appears. The last comparison leaves -2 unchanged.

    Initializing max to zero would return zero, which is not present. For a linear search, the corresponding stopping rule matters too: one mismatch does not establish failure; later elements still need checking.

  8. 8
    ChallengeKeep a search moving

    A search for 13 checks {11, 16, 13, 20}. After checking index 0 it returns -1 because 11 differs from 13. What should change, and where is the match?

    ---reveal---

    Keep checking after a mismatch and return -1 only after all candidates fail. Here index 1 also differs, but index 2 matches. A successful search can stop there. The failed first comparison describes one element, not the complete array.

  9. 9
    ConnectionMove the average into a function
    double mean(const int a[], size_t n);
    

    The array parameter shares access to the caller's elements. It does not carry their count, so the second parameter supplies it. const prevents accidental writes through a in this read-only operation.

    Compute the count where readings is declared and call mean(readings, n). Inside mean, sizeof a measures a pointer parameter instead of the caller's four-element array.

  10. 10
    ExampleHandle the empty range before dividing
    double mean(const int a[], size_t n) {
        if (n == 0) return 0.0;
        int sum = 0;
        for (size_t i = 0; i < n; i++) sum += a[i];
        return (double) sum / n;
    }
    

    For the four small readings, the total remains 60 and the result is 15.0. For n equal to zero, the chosen result is 0.0 and no array access or division occurs. The caller must pass an accurate usable count.

  11. 11
    ChallengeExplain a changed caller array

    A different function receives int a[], size_t n and sets every visited a[i] to zero. What happens to the caller's readings, and how could a total-only function prevent such writes?

    ---reveal---

    The caller's elements become zero because the function writes into the shared array storage. Its four positions still exist. A total-only function should use const int a[], making element assignments through a invalid. Changing a scalar copy of n would not change a caller's count variable.

  12. 12
    ChallengeUse only the readings that exist

    A buffer has capacity 10, but only four readings are in use. A new reading must be appended before computing a mean. What checks and count should the program use?

    ---reveal---

    Check that the used count, 4, is below capacity 10. Write at index 4, then update the count to 5. Pass that used count to mean so it processes exactly the five values. Passing the capacity would include positions that have not been supplied as readings.

  13. 13
    RecapConnect each count to its job

    A declaration creates the positions, a loop visits the used ones, and a function receives the usable count explicitly. A read-only parameter keeps calculations from overwriting input.

    When reviewing array code, track the first and last visited indexes and the state of each accumulator. For backward size_t loops, start at n, continue while i is positive, and access a[i - 1] so an empty range needs no subtraction from zero.

2

Turn stored bytes into text operations

16 steps
Build and transform a terminated string, then trace a multibyte UTF-8 character.

Save this lesson to follow every Walkthrough step by step.

  1. 1
    IntroductionWatch both text and storage
  2. 2
    ExplanationRead a character as a code
  3. 3
    ChallengeWrap within the alphabet
  4. 4
    ExampleBuild a writable string
  5. 5
    Key InsightMove the ending without moving storage
  6. 6
    ChallengeLeave an ending position
  7. 7
    ExampleSeparate reading from writing
  8. 8
    ExampleFinish the new string
  9. 9
    ChallengeReverse letters without the terminator
  10. 10
    TipClassify bytes with valid arguments
  11. 11
    ExampleLay out a UTF-8 accented letter
  12. 12
    ExplanationRecognize continuation bytes
  13. 13
    ChallengeMeasure a new encoded word
  14. 14
    ConnectionChoose operations that keep characters whole
  15. 15
    ChallengeCheck a proposed text shortcut
  16. 16
    RecapLocate the end and the character boundaries
3

Assemble, search and safely format text

14 steps
Check capacities, interpret search results and handle truncation and overlap.

Save this lesson to follow every Walkthrough step by step.

  1. 1
    IntroductionIdentify each function’s contract
  2. 2
    ExampleStart with a complete copy
  3. 3
    ExampleAppend at the existing ending
  4. 4
    ChallengeDecide whether another append fits
  5. 5
    ExplanationUse zero to recognize equal text
  6. 6
    ExampleRead from a found location
  7. 7
    ChallengeInspect a prefix-based command check
  8. 8
    TipExpect tokenization to change text
  9. 9
    ExampleFormat within a known capacity
  10. 10
    ChallengeInterpret the same format in less space
  11. 11
    Key InsightA bound may count source bytes
  12. 12
    ExampleMove a region that overlaps
  13. 13
    ChallengePreserve a complete result after shifting
  14. 14
    RecapSeparate bytes from text results
4

Read a table and choose a string-list layout

10 steps
Trace row and column totals, pass a table and reserve per-string space.

Save this lesson to follow every Walkthrough step by step.

  1. 1
    IntroductionGive each position two indexes
  2. 2
    ExamplePlace values into rows
  3. 3
    ExampleReset a row total at the row start
  4. 4
    ChallengeChange the grouping, keep the indexes
  5. 5
    ExplanationTell a function the row width
  6. 6
    ExampleReserve one terminated string per row
  7. 7
    Key InsightReferences permit different string lengths
  8. 8
    ChallengeReplace text in the right storage
  9. 9
    ChallengeKeep both dimensions valid in a function
  10. 10
    RecapKeep the table shape visible
5

Validate a command-line selection

11 steps
Interpret grouped arguments and convert a checked number into a usable selection.

Save this lesson to follow every Walkthrough step by step.

  1. 1
    IntroductionReceive text before interpreting it
  2. 2
    ExampleMap a shell command to entries
  3. 3
    ExplanationCheck the count before conversion
  4. 4
    ChallengeGroup one name correctly
  5. 5
    TipA returned integer is not enough
  6. 6
    ExampleInspect a complete conversion
  7. 7
    ExplanationCheck the selection’s own range
  8. 8
    ChallengeCatch text that looks partly numeric
  9. 9
    Key InsightSpelling and value are separate rules
  10. 10
    ChallengeCombine grouping and numeric validation
  11. 11
    RecapCheck presence, conversion and purpose
6

Repair a bounded input routine

13 steps
Trace line chunks, retain terminator space and copy only a complete accepted result.

Save this lesson to follow every Walkthrough step by step.

  1. 1
    IntroductionBound the write and check the result
  2. 2
    ExampleFind the extra write
  3. 3
    Key InsightA valid last position can hold the ending
  4. 4
    ChallengeCheck an index derived from a byte
  5. 5
    ExplanationRead a word with terminator space
  6. 6
    ExampleRead one bounded line chunk
  7. 7
    ExampleObserve where a long line is split
  8. 8
    TipInspect a newline before removing it
  9. 9
    ChallengeBudget for a complete line
  10. 10
    ExampleCheck a copy before writing
  11. 11
    ChallengeSeparate a safe prefix from an accepted answer
  12. 12
    ConnectionUse diagnostics with bounds reasoning
  13. 13
    RecapKeep four checks distinct

Arrays and Strings in C practice questions

60 Practice Questions in 6 sets

Set 1: Array values, loops and function calls10 Practice Questions

Question 1 of 10

What value is stored at a[3] after int a[5] = {6, 9};?
  1. 6
  2. 9
  3. 0
  4. An indeterminate value
Show answerHide answer
Answer: C
The initializer supplies the first two values, and C initializes the remaining elements to zero. Index 3 therefore contains 0. Indeterminate values would be a concern for a local array declared with no initializer, which is different from this partially initialized array.

Question 2 of 10

An array is declared as int a[7]; where int occupies 4 bytes. What pair gives sizeof a and the last valid index?
  1. 28 bytes; index 7
  2. 28 bytes; index 6
  3. 7 bytes; index 6
  4. 24 bytes; index 6
Show answerHide answer
Answer: B
Seven four-byte elements occupy 28 bytes, and zero-based indexing ends at 6. The last index is one less than the count. Using index 7 confuses the number of elements with the position of the last element and would access beyond this array.

Question 3 of 10

For int a[] = {-12, -4, -9};, what value should a maximum search return?
  1. -4
  2. 0
  3. -12
  4. -9
Show answerHide answer
Answer: A
The largest stored value is -4 because it is greater than both -12 and -9. A maximum search should start from a real element and update it when a larger one appears. Starting from zero would return a value that this all-negative array does not contain.

Question 4 of 10

What does this code print?

int a[] = {3, 8, 5, 2};
int sum = 0;
for (int i = 1; i < 4; i++) sum += a[i];
printf("%d", sum);
  1. 18
  2. 13
  3. 16
  4. 15
Show answerHide answer
Answer: D
The loop begins at index 1, so it adds 8, 5 and 2 to get 15. Index 0 is never visited. The total 18 includes the initial 3 and would be correct only for a loop starting at zero, which this code does not do.

Question 5 of 10

A search for 7 in {2, 9, 7, 4} returns -1 on the first unequal element. Where should its failure return occur?
  1. Inside the loop after any unequal candidate.
  2. After the loop has checked every candidate.
  3. Before checking the final candidate element.
  4. Immediately after the first loop increment.
Show answerHide answer
Answer: B
The first value, 2, does not match, but index 2 does contain 7. Failure is justified only when the loop finishes without a match. Returning after one mismatch treats evidence about one element as evidence about the entire array.

Question 6 of 10

What array results after swapping a[i] with a[5 - 1 - i] for i = 0 and i = 1, starting from {4, 7, 1, 8, 6}?
  1. {6, 7, 1, 8, 4}
  2. {4, 7, 1, 8, 6}
  3. {6, 8, 1, 7, 4}
  4. {8, 6, 1, 4, 7}
Show answerHide answer
Answer: C
The first swap exchanges 4 and 6; the second exchanges 7 and 8. The middle value 1 stays in place, producing the reversed array. The partially reversed result with 7 and 8 unchanged accounts for only the first swap, even though two swaps are specified.

Question 7 of 10

A function void set_first(int a[]) executes a[0] = 11;. What is the first element of the caller’s array after the call?
  1. It holds 11.
  2. It is unchanged.
  3. It is undefined.
  4. It holds 0.
Show answerHide answer
Answer: A
The parameter gives the function access to the caller’s elements, so this assignment changes the original first element to 11. There is no separate element copy to discard when the function returns. Passing an array differs here from passing a scalar value by itself.

Question 8 of 10

Inside void f(int a[], size_t n), pointers occupy 8 bytes and integers 4 bytes. What is sizeof a / sizeof a[0]?
  1. n, for every call
  2. 8, regardless of n
  3. 4, for every call
  4. 2, regardless of n
Show answerHide answer
Answer: D
The array parameter is adjusted to a pointer, so its size is 8 bytes in this environment. One element is 4 bytes, giving 2. That result does not recover the caller’s array length; the function must use the separately supplied n for its element count.

Question 9 of 10

A read-only function uses size_t for n and i. It must print an array backward and work for n = 0. What parameter and loop meet both requirements?
  1. const int a[]; start i at n - 1, test i >= 0, access a[i].
  2. int a[]; start i at n, test i > 0, access a[i].
  3. const int a[]; start i at n, test i > 0, access a[i - 1].
  4. int a[]; start i at n - 1, test i > 0, access a[i - 1].
Show answerHide answer
Answer: C
The const parameter prevents writes through a, while starting at n avoids unsigned subtraction when the range is empty. Accessing i - 1 then visits the last valid element down to zero. Starting at n - 1 with an unsigned i and testing i >= 0 wraps and never supplies a stopping condition.

Question 10 of 10

What does C guarantee after a program reads a[4] from int a[4]?
  1. No result is guaranteed.
  2. The read returns zero.
  3. The read returns a[3].
  4. The read always crashes.
Show answerHide answer
Answer: A
The array’s valid indexes are 0 through 3, so this read is out of bounds and has undefined behavior. C imposes no required result for that operation. A crash is possible, but expecting it every time wrongly treats one possible symptom as a language guarantee.
Set 2: Characters, string endings and UTF-810 Practice Questions

Save this lesson to see every answer and practice every set.

Question 1 of 10

A filter keeps only letters from "m8n2p" and writes them to a large enough output array. What must it write after the final p?
  1. A zero byte at output index 2
  2. A digit zero at output index 3
  3. A zero byte at output index 5
  4. A zero byte at output index 3
Show answerHide answer

Save this lesson to see this answer.

Question 2 of 10

What numeric value does '6' - '0' produce?
  1. 54
  2. 48
  3. 0
  4. 6
Show answerHide answer

Save this lesson to see this answer.

Question 3 of 10

In ASCII, what character results from ('W' - 'A' + 5) % 26 + 'A'?
  1. 'B'
  2. 'A'
  3. 'C'
  4. '\\'
Show answerHide answer

Save this lesson to see this answer.

Question 4 of 10

How should a char byte c be passed to isalpha without risking a negative byte argument?
  1. isalpha((signed char) c)
  2. isalpha(c - '0')
  3. isalpha((unsigned char) c)
  4. isalpha((int) c)
Show answerHide answer

Save this lesson to see this answer.

Question 5 of 10

What are the text length and array size of char color[] = "amber";?
  1. Length 6; size 6
  2. Length 5; size 6
  3. Length 5; size 5
  4. Length 6; size 5
Show answerHide answer

Save this lesson to see this answer.

Question 6 of 10

What string remains after char s[8] = "planet"; s[3] = '\0'; s[0] = 'P';?
  1. "Pla"
  2. "Planet"
  3. "Pla0et"
  4. "Plan"
Show answerHide answer

Save this lesson to see this answer.

Question 7 of 10

Why is char tag[4] = "east"; printf("%s", tag); unsafe?
  1. The initializer has too few characters.
  2. The format prints only a single byte.
  3. The array has no terminating zero byte.
  4. The array’s characters are read-only.
Show answerHide answer

Save this lesson to see this answer.

Question 8 of 10

A program reverses char s[] = "lamp"; by swapping s[i] with s[len - i] while i < len / 2, where len is strlen(s). What are the final strlen(s) and unchanged capacity?
  1. Length 4; capacity 5
  2. Length 0; capacity 5
  3. Length 0; capacity 4
  4. Length 5; capacity 5
Show answerHide answer

Save this lesson to see this answer.

Question 9 of 10

In UTF-8, precomposed "é" uses two bytes. What are strlen and the minimum storage for the string "écho"?
  1. 5 text bytes; 6 storage bytes
  2. 4 text bytes; 5 storage bytes
  3. 5 text bytes; 5 storage bytes
  4. 6 text bytes; 7 storage bytes
Show answerHide answer

Save this lesson to see this answer.

Question 10 of 10

A valid UTF-8 string has bytes 41 C3 B1 42 00. A program writes zero at byte index 2. What are the new strlen and UTF-8 validity of the text?
  1. Length 2; valid UTF-8
  2. Length 2; invalid UTF-8
  3. Length 3; valid UTF-8
  4. Length 1; invalid UTF-8
Show answerHide answer

Save this lesson to see this answer.

Set 3: Choose and check library operations10 Practice Questions

Save this lesson to see every answer and practice every set.

Question 1 of 10

After char s[] = "wxyz"; memmove(s, &s[1], 4);, what does s contain as a string?
  1. "xyzz"
  2. "wxy"
  3. "xyz"
  4. "yz"
Show answerHide answer

Save this lesson to see this answer.

Question 2 of 10

What is the smallest char-array capacity for a complete copy of "silver"?
  1. 7 bytes
  2. 6 bytes
  3. 8 bytes
  4. 5 bytes
Show answerHide answer

Save this lesson to see this answer.

Question 3 of 10

Can char label[10] = "blue"; strcat(label, "berry"); store its full result?
  1. No; the result needs 11 bytes.
  2. Yes; the result needs 9 bytes.
  3. Yes; the result needs 10 bytes.
  4. No; the result needs 12 bytes.
Show answerHide answer

Save this lesson to see this answer.

Question 4 of 10

Inside a function, an automatic local array is used as char out[20]; strcat(out, "go");. Why is this an invalid way to start building a string?
  1. out is too small for the appended text.
  2. out has no established string terminator.
  3. strcat requires a pointer array for out.
  4. strcat cannot copy from a string literal.
Show answerHide answer

Save this lesson to see this answer.

Question 5 of 10

What condition recognizes equal text in two valid strings a and b?
  1. a == b
  2. strcmp(a, b) > 0
  3. strlen(a) == strlen(b)
  4. strcmp(a, b) == 0
Show answerHide answer

Save this lesson to see this answer.

Question 6 of 10

After a successful char *p = strrchr("archive.backup.bin", '.');, what text begins at p?
  1. ".bin"
  2. "bin"
  3. ".backup.bin"
  4. "archive.backup"
Show answerHide answer

Save this lesson to see this answer.

Question 7 of 10

A writable array holds "on,,off;wait". Calls use strtok(s, ",;"), then repeat strtok(NULL, ",;") until NULL. What tokens are produced, and is s preserved?
  1. "on", "", "off", "wait"; s is modified.
  2. "on", "off;wait"; s is modified.
  3. "on", "off", "wait"; s is modified.
  4. "on", "off", "wait"; s is preserved.
Show answerHide answer

Save this lesson to see this answer.

Question 8 of 10

What does char b[6]; int r = snprintf(b, sizeof b, "%s:%d", "job", 84); store and return?
  1. It stores "job:8" and returns 5.
  2. It stores "job:84" and returns 6.
  3. It stores "job:" and returns 4.
  4. It stores "job:8" and returns 6.
Show answerHide answer

Save this lesson to see this answer.

Question 9 of 10

A seven-byte array starts with "AB". The program runs strncat(b, "123456", sizeof b - strlen(b) - 1). What is the resulting text and its stored byte count including zero?
  1. "AB12345"; 8 bytes
  2. "AB1234"; 7 bytes
  3. "AB123"; 6 bytes
  4. "AB123456"; 9 bytes
Show answerHide answer

Save this lesson to see this answer.

Question 10 of 10

Two double arrays compare equal element by element, but one stores positive zero where the other stores negative zero. What can a byte comparison establish?
  1. Whether their stored representations match.
  2. Whether their numeric values differ.
  3. Whether one has a larger element count.
  4. Whether both values are positive zero.
Show answerHide answer

Save this lesson to see this answer.

Set 4: Tables, text lists and startup input10 Practice Questions

Save this lesson to see every answer and practice every set.

Question 1 of 10

What is the last valid row and column pair for int table[4][6]?
  1. Row 4, column 6
  2. Row 3, column 5
  3. Row 3, column 6
  4. Row 4, column 5
Show answerHide answer

Save this lesson to see this answer.

Question 2 of 10

Where int occupies 4 bytes, what is sizeof table[0] for int table[3][5]?
  1. 12 bytes
  2. 15 bytes
  3. 60 bytes
  4. 20 bytes
Show answerHide answer

Save this lesson to see this answer.

Question 3 of 10

A table is {{2, 7, 4}, {6, 1, 5}}. A loop initializes sum once to zero before both loops, adds one row at a time, and prints after each row without resetting sum. What values print, and how is it corrected?
  1. 13 then 25; reset sum before each row.
  2. 13 then 12; reset sum before each row.
  3. 13 then 25; reset sum before each cell.
  4. 8 then 16; reset sum before each column.
Show answerHide answer

Save this lesson to see this answer.

Question 4 of 10

What fixed-width array parameter receives rows with seven integers each?
  1. int g[7][]
  2. int g[][]
  3. int g[][7]
  4. int g[][6]
Show answerHide answer

Save this lesson to see this answer.

Question 5 of 10

What is the smallest row width for char names[3][width] to store "Iris", "Magnolia" and "Ash" as complete ASCII strings?
  1. 8 bytes
  2. 10 bytes
  3. 16 bytes
  4. 9 bytes
Show answerHide answer

Save this lesson to see this answer.

Question 6 of 10

A program has char rows[2][5] = {"oak", "elm"}; and const char *refs[] = {"oak", "elm"};. It must replace the first listed word with complete text "cedar". What treatment is valid for each layout?
  1. Copy "cedar" into rows[0]; reassign refs[0].
  2. Reject the row copy; reassign refs[0] to "cedar".
  3. Reject the row copy; copy "cedar" into refs[0].
  4. Assign rows[0] = "cedar"; reassign refs[0].
Show answerHide answer

Save this lesson to see this answer.

Question 7 of 10

Under a normal shell, a program is run as ./mark "West Room" 9. What are argc and argv[2]?
  1. argc is 3; argv[2] is "9".
  2. argc is 4; argv[2] is "Room".
  3. argc is 2; argv[2] is NULL.
  4. argc is 3; argv[2] is "West Room".
Show answerHide answer

Save this lesson to see this answer.

Question 8 of 10

A program requires two user arguments. It checks argc == 2, then calls strlen(argv[2]). What count check and last index match its actual interface?
  1. Check argc == 2; read user arguments through index 2.
  2. Check argc == 3; read user arguments through index 3.
  3. Check argc == 3; read user arguments through index 2.
  4. Check argc == 2; read user arguments through index 1.
Show answerHide answer

Save this lesson to see this answer.

Question 9 of 10

After decimal strtol converts "42rest", what does the result and end pointer indicate?
  1. Value 0; no characters were consumed.
  2. Value 42; unconsumed text begins with r.
  3. Value 42; the whole input was consumed.
  4. Value 42; a range error must have occurred.
Show answerHide answer

Save this lesson to see this answer.

Question 10 of 10

A digits-only key fits in long but not in int. The program casts it to int before computing key % 26. How can it preserve the intended shift without narrowing first?
  1. Cast to int first, then compute the remainder.
  2. Use atoi, then compute the remainder as int.
  3. Compute the remainder as long, then cast the 0–25 result.
  4. Check digits again, then cast the original value.
Show answerHide answer

Save this lesson to see this answer.

Set 5: Input limits and complete results10 Practice Questions

Save this lesson to see every answer and practice every set.

Question 1 of 10

What largest %s width leaves a terminator position in char word[18]?
  1. %18s
  2. %16s
  3. %17s
  4. %19s
Show answerHide answer

Save this lesson to see this answer.

Question 2 of 10

A successful fgets reads from input "pine\n" into a 10-byte array. What are strlen of the result and the index of its terminator?
  1. Length 4; terminator index 4
  2. Length 5; terminator index 6
  3. Length 6; terminator index 5
  4. Length 5; terminator index 5
Show answerHide answer

Save this lesson to see this answer.

Question 3 of 10

The input line is "qrstuvwxy" followed by a newline. Two fgets calls use the same char b[6] without other reads. What text does the second call store?
  1. "uvwxy"
  2. "vwxy\n"
  3. "vwxy"
  4. "qrst\n"
Show answerHide answer

Save this lesson to see this answer.

Question 4 of 10

A program allows 12 ASCII text characters in a name entered as a complete newline-terminated line. It must inspect the newline and then store only the name in a separate array. What minimum input and final capacities are needed?
  1. Input 14 bytes; final 13 bytes
  2. Input 13 bytes; final 13 bytes
  3. Input 14 bytes; final 12 bytes
  4. Input 13 bytes; final 12 bytes
Show answerHide answer

Save this lesson to see this answer.

Question 5 of 10

A 9-byte buffer receives a successful fgets result of length 8 with no newline. No further input has been inspected. What can be concluded about the logical line?
  1. It is definitely longer than eight characters.
  2. It may end there or have more input waiting.
  3. It definitely ended at end-of-file already.
  4. Its newline was read and removed by fgets.
Show answerHide answer

Save this lesson to see this answer.

Question 6 of 10

Why is counts[c - 'a']++ unsafe for unchecked character input?
  1. The derived index can be outside the array.
  2. The increment cannot update integer counters.
  3. The subtraction always returns a negative value.
  4. The expression compares two whole strings.
Show answerHide answer

Save this lesson to see this answer.

Question 7 of 10

A six-byte char array is filled at indexes 0 through 5 with letters, then passed to strlen. What property is missing?
  1. An unused byte beyond the array for zero.
  2. An explicit newline after the final letter.
  3. A terminator within the six valid positions.
  4. A separate integer storing its byte capacity.
Show answerHide answer

Save this lesson to see this answer.

Question 8 of 10

A program stores a complete code in char out[6]. The call snprintf(out, sizeof out, "%s%d", "R-", 1234) is followed by if (r > sizeof out) reject();, where r is its nonnegative return. What is stored, does that test reject it, and what must change?
  1. "R-1234"; accepted; keep the existing test.
  2. "R-123"; rejected; keep the existing test.
  3. "R-12"; accepted; reject when r >= capacity.
  4. "R-123"; accepted; reject when r >= capacity.
Show answerHide answer

Save this lesson to see this answer.

Question 9 of 10

A decimal parser rejects ERANGE and requires *end == '\0', but does not check end == start. What invalid input can still be accepted as zero?
  1. An empty string
  2. An alphabetic word
  3. A digit then a letter
  4. A number beyond long
Show answerHide answer

Save this lesson to see this answer.

Question 10 of 10

A valid UTF-8 string has bytes 41 C3 A9 E2 82 AC 00. A program copies it into char b[5], reserving its last position for zero. What are the resulting strlen, starting-byte count and encoding validity?
  1. Length 4; starts 3; valid UTF-8.
  2. Length 4; starts 3; invalid UTF-8.
  3. Length 3; starts 3; valid UTF-8.
  4. Length 4; starts 2; invalid UTF-8.
Show answerHide answer

Save this lesson to see this answer.

Set 6: Trace and repair combined C operations10 Practice Questions

Save this lesson to see every answer and practice every set.

Question 1 of 10

What array is created by int a[4] = {[2] = 9};?
  1. {0, 9, 0, 0}
  2. {0, 0, 9, 0}
  3. {9, 0, 0, 0}
  4. {0, 0, 0, 9}
Show answerHide answer

Save this lesson to see this answer.

Question 2 of 10

An eight-slot array has count 5. One value is appended at a[count], and count is incremented. What index was written and what is the new count?
  1. Index 5; count 6
  2. Index 4; count 6
  3. Index 6; count 6
  4. Index 5; count 5
Show answerHide answer

Save this lesson to see this answer.

Question 3 of 10

For int g[3][4], what is the zero-based element position of g[2][1] in row-major storage?
  1. 7
  2. 8
  3. 10
  4. 9
Show answerHide answer

Save this lesson to see this answer.

Question 4 of 10

A shell runs ./pick "" blue. How do argv[1] and argv[argc] differ?
  1. argv[1] is a null pointer; argv[argc] is an empty string.
  2. Both entries are empty strings with their own terminators.
  3. argv[1] is an empty string; argv[argc] is a null pointer.
  4. Both entries are null pointers marking absent text.
Show answerHide answer

Save this lesson to see this answer.

Question 5 of 10

A function receives int a[], size_t n and executes a[0] = 0; n = 0;. The caller supplied {5, 8, 2} and count 3, then computes the integer sum of its elements divided by its count. What average and caller count result?
  1. Average 0; caller count 3
  2. Average 3; caller count 3
  3. Average 5; caller count 3
  4. Undefined division; caller count 0
Show answerHide answer

Save this lesson to see this answer.

Question 6 of 10

A parser splits writable text "run,runner" at commas using strtok. It increments a counter when strncmp(token, "run", 3) == 0. What count results, what count should exact-command matching give, and what comparison fixes it?
  1. Current 2; exact 1; strcmp(token, "run") == 0.
  2. Current 1; exact 1; strcmp(token, "run") == 0.
  3. Current 2; exact 2; strncmp(token, "run", 2) == 0.
  4. Current 1; exact 2; strstr(token, "run") != NULL.
Show answerHide answer

Save this lesson to see this answer.

Question 7 of 10

A five-byte array b is filled by strncpy(b, "planet", sizeof b);. The program requires the complete original word. What repair satisfies the requirement?
  1. Write zero into b[4] after copying.
  2. Append zero using b[5] after copying.
  3. Pass sizeof b - 1, then use b unchanged.
  4. Reject it as too long before copying.
Show answerHide answer

Save this lesson to see this answer.

Question 8 of 10

A program uses int values[4] = {14, 28, 35, 49};. It fully converts "04" with decimal strtol, accepts selections 1–4, then reads values[selected]. It must allow all four selections and map selection 1 to the first value. What happens, and what index rule fixes it?
  1. It prints 49; keep index selected.
  2. It prints 49; use index selected - 1.
  3. It has undefined behavior; use index selected - 1.
  4. It has undefined behavior; require selection < 4.
Show answerHide answer

Save this lesson to see this answer.

Question 9 of 10

A function read_text(char out[], size_t cap) calls fgets(out, sizeof out, stdin). The caller passes a 30-byte array; pointer size is 8. With "sunflower\n" waiting, what chunk is stored, what remains, and what bound should replace sizeof out?
  1. "sunflower\n"; nothing remains; use cap.
  2. "sunflowe"; "r\n" remains; use cap.
  3. "sunflow"; "er\n" remains; use strlen(out).
  4. "sunflow"; "er\n" remains; use cap.
Show answerHide answer

Save this lesson to see this answer.

Question 10 of 10

An ASCII letter-run counter starts outside a word and increments whenever a letter follows a non-letter. How many words does it count in "go2fast now!"?
  1. 3
  2. 2
  3. 4
  4. 1
Show answerHide answer

Save this lesson to see this answer.

Stuck on your own question? Homework Help

Key Terms

85 Key Terms, each with its definition

A

Accumulator
An accumulator is a variable that gathers a running result, such as a sum or count, as a loop processes successive values.
Starting sum at zero lets each sum += a[i] add the next array element to the current total.
Application range
An application range is the interval of values a program permits for a purpose, which may be narrower than the numeric type’s full range.
A converted count of 120 fits in an integer but fails a program rule requiring a count from 1 through 100.
argc
The parameter argc gives the count of argument strings supplied to main, normally including the string used to launch the program.
Running ./label red blue under a normal shell gives argc equal to 3: the launch name and two colors.
argv
The parameter argv provides access to argument strings by index, followed by a null pointer at the position given by argc.
After checking argc, a program can use strcmp(argv[1], "-v") to recognize its first option.
Array
An array is a fixed-size sequence of neighboring elements of one type, accessed through a shared name and an integer index.
int levels[6] reserves six integers; updating levels[2] changes the third stored value.
Array of string pointers
An array of string pointers stores one reference per string, allowing text stored elsewhere to have different lengths.
const char *names[] = {"Al", "Mina"} stores two references and allows a reference to be replaced.
Array parameter
An array parameter is adjusted to a pointer parameter, giving a function access to caller-owned elements without their count.
A function declared with int a[] still needs a separate length before it can loop over its input.
ASCII
ASCII is a character encoding assigning values 0 through 127 to basic letters, digits, punctuation and control characters.
The arithmetic '8' - '0' produces 8 because digit character codes are consecutive.
atoi
The function atoi converts an initial numeric portion to int without a reliable error report and has undefined behavior on overflow.
The input "18x" produces 18 with atoi, so a returned number does not establish that the full input was numeric.

B

Bit mask
A bit mask selects particular bit positions for testing; an AND operation keeps only the input bits marked by ones in the mask.
The mask 0xC0 keeps the top two bits so a program can recognize the UTF-8 continuation pattern.
Buffer
A buffer is a region of storage reserved for data being read, written or transformed, with a capacity that limits valid writes.
A 32-byte name buffer can hold thirty ASCII text characters, a retained newline and a final zero byte.
Buffer overflow
A buffer overflow is a write beyond a buffer’s available storage, potentially corrupting nearby data or program control information.
Copying six text bytes and a terminator into a six-byte array overruns that array by one byte.
Byte order comparison
A byte order comparison orders strings by unsigned stored byte values, which can differ from the alphabetical conventions of a language.
In UTF-8, strcmp puts an accented Latin letter after ASCII z because its leading byte has a higher value.

C

C string
A C string is a sequence of characters ending at a null character, with text stored in an array rather than a separate built-in string type.
The array initialized by char pet[] = "dog" contains three letters followed by a zero-valued character.
Caesar shift
A Caesar shift replaces each letter with another a fixed number of alphabet positions away, wrapping within its letter range.
On ASCII text, shifting uppercase Y by two wraps to A while a digit or comma can remain unchanged.
Capacity
An array’s capacity is the number of elements its allocated storage can hold, regardless of how many currently contain useful values.
An array with capacity 80 and count 29 has room for 51 additional values if the count is maintained correctly.
Character classification
Character classification tests a character code for a category, such as a digit, letter or whitespace, using functions in <ctype.h>.
isdigit((unsigned char) c) gives a nonzero result when the stored byte represents a decimal digit.
Character constant
A character constant is a single-quoted C expression with type int that represents a character code, such as the code for a letter.
With ASCII codes, printf("%c %d", 'B', 'B') displays the letter B and the numeric value 66.
Character encoding
A character encoding defines a mapping between text and its stored numeric representation, including the bytes used for transmission or storage.
A C string containing the same visible word can need different byte counts under different encodings.
Column
A column groups the same within-row position across the rows of a table; its index is the second index in g[r][c].
To total column 2, keep c equal to 2 and vary r across every row while reading g[r][c].
Command-line argument
A command-line argument is a string supplied when a program starts, often grouped or expanded by a shell before the program receives it.
Quoting "North Hall" on the command line can provide one argument containing a space instead of two words.
Concatenation
Concatenation joins text sequences in order, requiring enough destination space for the combined text and its terminating character.
Joining "north" and "east" forms "northeast", which needs ten bytes as a terminated ASCII string.
Const-qualified parameter
A const-qualified parameter prevents modifying the referenced elements through that parameter while still allowing them to be read.
A total function uses const int a[] because adding the input values does not require changing them.
Continuation byte
A UTF-8 continuation byte has the leading bit pattern 10 and follows a leading byte within a multibyte encoded code point.
The byte 0xA9 continues the sequence for "é"; it must not be kept alone as a complete UTF-8 character.
Conversion count
A conversion count is the number of input assignments scanf successfully completed, used to check whether requested data was actually read.
A one-number read expects a return of 1; a matching failure can leave the same invalid bytes waiting in input.

D

Delimiter
A delimiter is a character used to separate parts of text, such as the comma or space characters supplied to a tokenizer.
With delimiters ", ", the text "one, two" can be split into tokens "one" and "two".

E

Element
An element is one value stored in an array, with the same declared type as the other values and its own indexed position.
In int a[3] = {8, 1, 6}, the element a[1] holds 1 and can be assigned another integer.
Empty string
An empty string contains no text characters and is represented by a terminator in its first position, giving it a length of zero.
char message[20] = "" provides a valid empty destination for a later append that fits its capacity.
End pointer
An end pointer records where numeric conversion stopped, allowing a program to detect no conversion or unwanted remaining characters.
For input "9z", strtol returns 9 but the end pointer refers to the z that was not consumed.
End-of-file
End-of-file marks that no further input is available; fgets can still succeed if it read characters before reaching that condition.
A file ending with "done" and no newline still lets a sufficiently large fgets buffer receive that final text.
ERANGE
The macro ERANGE identifies a range error reported through errno, such as a strtol result too large or small for long.
Set errno to zero before conversion, then test for ERANGE before accepting a long digit sequence.
Escape sequence
An escape sequence is a backslash-based source spelling for a character that needs special notation, such as newline or a quote.
The literal "A\nB" contains a newline between its letters; the backslash and n encode one character.
Exit status
An exit status reports program completion to its calling environment; returning zero from main signals successful completion.
A missing required argument can make a program print usage information and return a nonzero failure status.

F

fgets
The function fgets reads bounded line input, retaining a reached newline and adding a terminator when it successfully reads text.
An eight-byte buffer can receive seven input characters at most, leaving additional line text for later reads.
Field width
A field width in a %s conversion limits the number of input characters stored before the additional terminating zero byte.
For char word[10], the conversion %9s reserves the tenth available byte for the terminator.
Filtering
Filtering builds a result from selected input characters, using a separate write position because rejected characters occupy no output space.
Keeping only letters from "q2r7" writes "qr" to the output and then adds its terminating zero byte.
Fixed-width string list
A fixed-width string list stores text in rows of a 2D char array, with each row reserving its own fixed capacity and terminator space.
In char labels[5][12], every label has twelve bytes of storage even when its current text is shorter.
Frequency array
A frequency array stores one counter for each category, using a validated category value to select the counter to increment.
After converting a digit to value 4, counts[4]++ adds one occurrence to that digit’s tally.

I

In-place transformation
An in-place transformation changes values in their existing storage instead of constructing a separate output array.
Uppercasing a writable ASCII string can assign each converted character back to the same indexed position.
Index
An index is an integer position used to select an array element, starting at zero and ending one below the element count.
For a seven-element array, index 6 reaches the last element; index 7 is already outside it.
Initializer
An initializer supplies starting values when an object is declared; missing values in an array initializer become zero.
int hits[4] = {3} initializes the first counter to 3 and the other three counters to zero.

L

Linear search
A linear search checks elements in sequence until it finds a target or has examined every candidate without a match.
Searching {3, 8, 2} for 2 succeeds at index 2 after the earlier unequal elements are checked.
Locale
A locale supplies conventions used by library functions; the default "C" locale gives single-byte classification its basic character rules.
Bytewise toupper in the "C" locale does not turn a complete UTF-8 accented letter into its uppercase form.

M

memcmp
The function memcmp compares a fixed number of bytes and reports their ordering, testing storage representations rather than numeric values.
Positive and negative floating-point zero can compare equal as values while memcmp finds different stored bytes.
memcpy
The function memcpy copies exactly a requested number of bytes between nonoverlapping regions without interpreting string terminators.
memcpy(b, a, sizeof a) copies the full storage of array a when b has enough room and is a separate region.
memmove
The function memmove copies a requested byte count with defined behavior even when source and destination regions overlap.
Moving three bytes within the same character array requires an overlap-safe operation such as memmove.
memset
The function memset fills a region with a repeated byte value, without assigning that numeric value separately to each array element.
memset(counts, 0, sizeof counts) zeroes an integer array; byte value 1 would not assign integer 1.

N

Nested loops
Nested loops place one loop inside another so the inner loop repeats for every outer-loop value, such as columns within each row.
A two-row, five-column traversal runs the inner body ten times when both indexes stay within their bounds.
Null pointer
A null pointer is a pointer value that refers to no object; search functions use it to signal that no matching location exists.
Check strchr(name, '.') != NULL before using its result as the beginning of a filename suffix.
Null terminator
A null terminator is the zero-valued character marking the end of a C string, occupying storage without adding to its text length.
Writing text[2] = '\0' makes string functions stop after the first two characters in that array.
Numeric conversion
A numeric conversion interprets text as a number, requiring separate checks for accepted spelling, consumed text and permitted value range.
The bytes "27" need conversion before a program can reduce the numeric value with % 26.

O

Off-by-one error
An off-by-one error uses a boundary one position too far or too short, often from confusing an element count with its last valid index.
The condition i <= n makes a zero-based n-element traversal attempt one extra access at index n.
Out-of-bounds access
An out-of-bounds access reads or writes an indexed position outside the actual array or row being accessed and has undefined behavior.
Accessing a[8] in an eight-element array is invalid even if a test run seems to return a useful value.
Output array
An output array is storage supplied by a caller for a function to fill with multiple results, with an agreed capacity or count.
A squares function can write each result into out[i] while the caller keeps ownership of the array.

P

Partial line
A partial line is the portion read before a buffer’s size limit stops input, potentially leaving more of that logical line unread.
A long response can arrive in two fgets chunks, so the second chunk should not become a new answer.

R

Row
A row is one inner array within a two-dimensional array, containing a fixed number of consecutive elements of the same type.
In int table[2][5], table[1] denotes the second row, which contains five integer positions.
Row width
The row width is the number of elements in each inner array and is required in a table parameter to locate indexed elements.
The parameter int g[][6] accepts rows six integers wide, while the caller supplies the row count separately.
Row-major order
Row-major order stores all elements of one row consecutively before the next row, as required for C’s multidimensional arrays.
The element following grid[0][3] in a four-column table’s storage is grid[1][0].

S

size_t
The type size_t is an unsigned integer type used for object sizes and returned by sizeof; it is commonly used for array lengths.
printf("%zu", sizeof data) uses the size format to print the number of bytes occupied by data.
snprintf
The function snprintf formats text within a supplied capacity and returns the full required text length or a negative error result.
A return of 12 for capacity 10 means the stored result was cut short, despite remaining terminated.
strcat
The function strcat appends a source string at a destination’s existing terminator and writes a new terminator after the combined text.
Appending "box" to "red " needs eight bytes of destination storage, including the final zero byte.
strchr
The function strchr finds the first matching character in a string and returns its location, or a null pointer if no match exists.
Searching "name.part.ext" for a dot with strchr finds the suffix ".part.ext", including the first dot.
strcmp
The function strcmp compares terminated strings as unsigned bytes and returns a negative, zero or positive result according to their order.
strcmp("oak", "oak") == 0 tests equal contents even when the two strings occupy separate storage.
strcpy
The function strcpy copies a complete source string and its terminator into a nonoverlapping destination with enough available storage.
Copying "pine" writes five bytes including zero, so it fits a destination declared as char out[5].
strcspn
The function strcspn measures the initial part of a string containing none of the characters supplied in its second argument.
strcspn("fir:oak", ":,") returns 3 because the first listed separator occurs after the three letters of fir.
String length
A string length is the number of text bytes before the first null character, excluding that character and any unused capacity.
A 40-byte array containing "June" has length 4, although its available storage remains 40 bytes.
String literal
A string literal is double-quoted text that supplies a terminated character array whose contents the running program must not modify.
const char *label = "ready" refers to a literal; a writable copy can use char label[] = "ready".
strlen
The function strlen scans a valid string to its terminator and returns the number of preceding bytes as a size_t value.
For char tag[30] = "oak", strlen(tag) is 3 while sizeof tag reports the full 30-byte capacity.
strncat
The function strncat appends at most a specified number of source text bytes and then writes a terminator after them.
For a valid destination string, remaining text room is capacity minus its current length minus one.
strncmp
The function strncmp compares no more than a specified number of string bytes, stopping sooner at an ending or a difference.
A three-byte match between "run-fast" and "run-slow" establishes a shared prefix, not equal complete strings.
strncpy
The function strncpy copies up to a bound and pads shorter text with zeros, but does not terminate when the source fills that bound.
Copying "abcd" with bound 3 stores a, b and c without a zero byte, so another step must terminate the result.
strrchr
The function strrchr finds the last matching character in a string and returns its location, or a null pointer if no match exists.
The last dot in "name.part.ext" begins ".ext", so strrchr can locate the extension separator.
strtol
The function strtol converts text to long using a chosen base and reports a stopping location and range error information.
Calling strtol(text, &end, 10) lets the program reject a leftover letter after otherwise valid decimal digits.
Substring
A substring is a consecutive sequence of characters within another string; strstr finds its first matching starting location.
Searching "blue-green" for "green" returns a location from which the remaining string reads "green".

T

Tokenization
Tokenization divides text into pieces separated by delimiters; strtok does this by modifying the input and remembering a position.
A second strtok(NULL, ",") call continues splitting the same writable input instead of restarting it.
Truncation
Truncation is loss of text beyond an available length limit, which can avoid an overflow while still changing the meaning of the data.
A shortened file label may refer to different text, so a complete-name interface should reject a truncated result.
Two-dimensional array
A two-dimensional array is an array of equal-width row arrays, with two indexes selecting one element by its row and column.
int board[4][6] provides 24 integers, reached through four valid row indexes and six valid column indexes.

U

Undefined behavior
Undefined behavior means the C standard imposes no requirements on the result of an operation, so no particular outcome is guaranteed.
An invalid array write can appear harmless in one build and crash or corrupt data in another build.
Unicode code point
A Unicode code point is a numbered position in Unicode’s code space, used to identify characters and other assigned text values.
The precomposed letter "é" is U+00E9, even though its UTF-8 storage consists of two separate bytes.
Unsigned wraparound
Unsigned wraparound reduces an unsigned arithmetic result modulo its type’s range, so subtracting one from zero gives a large value.
A size_t countdown cannot stop by becoming negative; use i > 0 and access position i - 1.
Used count
The used count records how many array elements currently belong to the active data and determines the processing loop’s bound.
If only 12 entries have been filled in a 50-element array, a data-processing loop stops at count 12.
UTF-8
UTF-8 is a Unicode encoding that stores each scalar value in one to four bytes and preserves one-byte ASCII encodings.
The string "café" occupies five UTF-8 text bytes, plus one zero byte when stored as a C string.

V

Variable-length array
A variable-length array gets its size when its declaration executes and keeps that size until its lifetime ends.
In C17, a local const int n = 6; int a[n]; creates a VLA because n is not a constant expression.
Variably modified parameter
A variably modified parameter uses earlier size parameters in its array type so a function can receive tables with different row widths.
GCC and Clang accept int g[rows][cols] after parameters defining positive matching row and column counts.

W

Word boundary
A word boundary is the transition used by a program to decide where a word begins or ends, according to its chosen definition of a word.
A letter-run counter starts a new word when it sees a letter after a space, punctuation or the string’s start.

About this lesson

By StudeliReport a mistake

Make your own Study Materials

Turn your notes, slides and documents into Study Guides, Practice Questions, Flashcards and 6 more tools.

Get Started