student@ubuntu:~$
c-foundations Lesson 10 9 min read

Arrays & Strings

Fixed-size arrays, null-terminated strings, and the functions that operate on them

Reading: Hanly & Koffman: §7.1–7.2 (pp. 378–383), §8.1–8.4 (pp. 456–479)

Quick check before you start: Can you explain what happens when you access arr[10] on a size-5 array in Java? If you said “ArrayIndexOutOfBoundsException,” you already know the safety net C does not provide. Read on.

Practice this topic: Arrays and Strings skill drills

After this lesson, you will be able to:

  • Declare and initialize C arrays, including zero-filling with = {0}
  • Use sizeof(arr) / sizeof(arr[0]) to compute element count and explain why it fails inside functions
  • Explain that C strings are char arrays terminated by '\0'
  • Use strlen, strcmp, strcpy, and strcat from <string.h>
  • Read string input safely with fgets and strip the trailing newline

Arrays: No Safety Net

In Java, accessing arr[100] on a size-10 array throws ArrayIndexOutOfBoundsException. In C, the same access does… something. Maybe it reads garbage. Maybe it overwrites another variable. Maybe it crashes. C does not check array bounds. Ever.

Declaring Arrays

int scores[5];                         // 5 ints, UNINITIALIZED (garbage!)
int grades[5] = {90, 85, 77, 92, 88}; // Initialized
int zeros[100] = {0};                  // First element 0, rest auto-filled with 0

Common Pitfall: An uninitialized C array contains whatever garbage data happened to be in that memory. Always initialize your arrays — either with values or with = {0} to zero-fill.

No .length Property

Unlike Java, C arrays do not know their own size. You must track it yourself:

int grades[5] = {90, 85, 77, 92, 88};
int size = sizeof(grades) / sizeof(grades[0]);  // 20 / 4 = 5

for (int i = 0; i < size; i++)
{
    printf("%d\n", grades[i]);
}

The sizeof trick divides the total bytes of the array by the size of one element. But this only works where the array was declared — not inside functions that receive the array as a parameter. Inside a function, sizeof(arr) returns the size of a pointer (8 bytes on 64-bit systems), not the array.

Arrays and Functions

When you pass an array to a function, you must also pass its size:

void print_array(const int arr[], int size)
{
    for (int i = 0; i < size; i++)
    {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

From Java: In Java, arrays are objects with a .length field and bounds checking. In C, arrays are contiguous blocks of memory. No length, no checking, no exceptions. You pass the size as a separate parameter, and you are responsible for staying within bounds.

When you pass an array to a function, the function receives a pointer to the original array, not a copy. Modifying array elements inside the function affects the caller’s array. Use const when a function only reads the array.


Strings: Character Arrays with a Null Terminator

C has no String class. A “string” in C is an array of char that ends with a special byte: '\0' (the null terminator).

char greeting[] = "Hello";

C stores this in memory:

Index:   [0]  [1]  [2]  [3]  [4]  [5]
Value:   'H'  'e'  'l'  'l'  'o'  '\0'

The array is 6 bytes — 5 characters plus the null terminator. Every string function scans forward until it hits that '\0'. If it is missing, your program reads off into random memory.

String Library Functions (<string.h>)

strlen — string length (not counting '\0'):

char name[] = "Alice";
printf("%zu\n", strlen(name));    // 5 (not 6!)

strcmp — compare two strings:

if (strcmp(name, "Alice") == 0)       // Equal
if (strcmp(name, "Bob") < 0)          // name comes before "Bob" alphabetically

Common Pitfall: Never use == to compare strings in C. if (str1 == str2) compares memory addresses, not contents. Two identical strings stored in different locations would compare as “not equal.” Always use strcmp(str1, str2) == 0.

strcpy — copy one string to another:

char dest[50];
strcpy(dest, "Hello");             // dest is now "Hello"

strcat — concatenate (append) strings:

char greeting[50] = "Hello, ";
strcat(greeting, "world!");        // greeting is now "Hello, world!"

Both strcpy and strcat do not check whether the destination buffer is large enough. Copying a 100-character string into a 50-character buffer overflows silently.

Safe String Input with fgets

scanf("%s") stops at whitespace and has no overflow protection. Use fgets instead:

char line[100];
fgets(line, sizeof(line), stdin);

fgets reads at most sizeof(line) - 1 characters, always adds '\0', and includes the newline if the line fits. Strip the trailing \n:

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

Java vs. C Strings

Operation Java C
Declare String s = "hello"; char s[50] = "hello";
Length s.length() strlen(s)
Compare s.equals("hello") strcmp(s, "hello") == 0
Copy s2 = s; strcpy(s2, s);
Concatenate s + " world" strcat(s, " world");
Read line scanner.nextLine() fgets(s, size, stdin);

From Java: Java’s String is an immutable object with methods. C’s “string” is a mutable char array with library functions. There is no garbage collection, no automatic resizing, no bounds checking. You manage the buffer, you check the bounds, you track the null terminator.


Check Your Understanding
You declare char name[5] = "Alice";. What happens when you call printf("%s", name);?
AIt prints "Alice" normally — the string fits in 5 bytes
BIt prints "Alice" followed by garbage characters — there is no room for the null terminator
CThe compiler refuses to compile this code
DIt prints only "Alic" — the last character is replaced by the null terminator
Answer: B. The string "Alice" requires 6 bytes: 5 for the characters plus 1 for the null terminator '\0'. A char[5] array can only hold 5 bytes, so the null terminator is missing. printf("%s") scans forward until it finds a '\0', reading past the array into whatever garbage is in adjacent memory. This is undefined behavior. Use char name[6] or char name[] = "Alice"; to let the compiler allocate the correct size.

What Comes Next

You now know how C handles collections (arrays) and text (null-terminated character arrays). In the next lesson, you will learn how to split programs across multiple files with headers and Makefiles, and how to accept command-line arguments with argc and argv.