Skip to content

C Strings — Char Arrays, Null Terminator, and string.h Functions

DodaTech Updated 2026-06-28 8 min read

In this tutorial, you will learn about C Strings. We cover key concepts, practical examples, and best practices to help you master this topic.

C strings are sequences of characters stored in char arrays, terminated by a null character ('\0'). Unlike higher-level languages, C has no built-in string type -- strings are simply arrays of char with a special terminator.

Why It Matters

String handling is one of the most error-prone areas in C Programming. Buffer overflows, off-by-one errors, and null terminator issues have caused countless security vulnerabilities. Understanding exactly how strings work in memory is essential for writing safe and correct C code. Every program that reads input, prints output, or processes text uses strings.

Real-World Use

Every printf call uses format strings. File paths are strings. Network protocols send and receive text. Configuration files contain strings. The Heartbleed bug in OpenSSL was a string handling error. Durga Antivirus Pro parses file paths and virus signature strings constantly.

What You Will Learn

  • Character arrays and string literals
  • The null terminator and why it matters
  • String input and output with printf, puts, gets, fgets
  • String.h functions: strlen, strcpy, strcat, strcmp, strtok
  • Safe string handling with strncpy, strncat, snprintf
  • Common string operations and pitfalls

Learning Path

flowchart LR
  A[Arrays] --> B[Strings
You are here] B --> C[Pointers Basics] C --> D[Pointer Arithmetic] D --> E[Dynamic Memory] style B fill:#f90,color:#fff

What Is a C String?

A string in C is a char array that ends with a null character \0 (ASCII value 0):

#include <stdio.h>

int main() {
    // String literal -- automatically null-terminated
    char greeting[] = "Hello";
    
    // Equivalent explicit initialization
    char explicit[6] = {'H', 'e', 'l', 'l', 'o', '\0'};
    
    // sizeof includes the null terminator
    printf("Size of 'Hello': %zu bytes\n", sizeof(greeting));  // 6
    
    // strlen counts characters before null terminator
    printf("Length of 'Hello': %zu\n", strlen(greeting));      // 5
    
    char name[] = "Alice";
    printf("Hello, %s!\n", name);  // Hello, Alice!
    
    return 0;
}

Expected output:

Size of 'Hello': 6 bytes
Length of 'Hello': 5
Hello, Alice!

The null terminator is what distinguishes a string from a plain char array. Any char array that does not contain \0 is not a valid C string.

String Literals vs Char Arrays

#include <stdio.h>

int main() {
    // String literal -- stored in read-only memory
    char *str1 = "Hello";
    
    // Char array -- stored on stack, modifiable
    char str2[] = "Hello";
    
    // str1[0] = 'h';  // Undefined behavior! String literal is read-only
    
    str2[0] = 'h';  // OK: str2 is a modifiable copy
    printf("Modified: %s\n", str2);  // hello
    
    // Array vs pointer difference
    printf("Size of pointer: %zu\n", sizeof(str1));  // 8 (pointer size)
    printf("Size of array: %zu\n", sizeof(str2));    // 6 (array size)
    
    return 0;
}

Expected output:

Modified: hello
Size of pointer: 8
Size of array: 6

String Input

Reading strings from user input requires care to avoid buffer overflow:

#include <stdio.h>

int main() {
    char buffer[20];
    
    // DANGEROUS: gets() has no bounds checking
    // gets(buffer);  // Never use gets()!
    
    // SAFE: fgets() limits input to buffer size - 1
    printf("Enter your name: ");
    fgets(buffer, sizeof(buffer), stdin);
    
    // fgets includes the newline; remove it
    buffer[strcspn(buffer, "\n")] = '\0';
    
    printf("Hello, %s!\n", buffer);
    
    // scanf with width specifier
    printf("Enter a word: ");
    scanf("%19s", buffer);  // Read at most 19 chars
    printf("Word: %s\n", buffer);
    
    return 0;
}

String.h Functions

The <string.h> header provides essential string functions:

#include <stdio.h>
#include <string.h>

int main() {
    char dest[50];
    char src[] = "Hello";
    
    // strlen: get string length (without null terminator)
    printf("Length of '%s': %zu\n", src, strlen(src));  // 5
    
    // strcpy: copy string (DANGEROUS without size check)
    strcpy(dest, src);
    printf("Copied: %s\n", dest);
    
    // strncpy: safe copy with size limit
    char safe[10];
    strncpy(safe, "This is a long string", sizeof(safe) - 1);
    safe[sizeof(safe) - 1] = '\0';  // Ensure null termination
    printf("Safe copy: %s\n", safe);
    
    // strcat: concatenate (DANGEROUS)
    char greeting[50] = "Hello, ";
    strcat(greeting, "World!");
    printf("Concatenated: %s\n", greeting);
    
    // strncat: safe concatenation
    char msg[20] = "Hi ";
    strncat(msg, "there everyone!", sizeof(msg) - strlen(msg) - 1);
    printf("Safe concat: %s\n", msg);
    
    // strcmp: compare strings (returns 0 if equal)
    char pass[] = "secret";
    char input[] = "secret";
    if (strcmp(pass, input) == 0) {
        printf("Password correct!\n");
    }
    
    // strcmp ordering ('A' < 'B' < 'Z' < 'a' < 'z')
    printf("'apple' vs 'banana': %d\n", strcmp("apple", "banana"));  // < 0
    
    return 0;
}

Expected output:

Length of 'Hello': 5
Copied: Hello
Safe copy: This is a
Concatenated: Hello, World!
Safe concat: Hi there everyon
Password correct!
'apple' vs 'banana': -1

Searching and Tokenizing

#include <stdio.h>
#include <string.h>

int main() {
    char text[] = "Hello, world! Welcome to C programming.";
    char *pos;
    
    // strchr: find first occurrence of character
    pos = strchr(text, 'w');
    if (pos) {
        printf("Found 'w' at position: %ld\n", pos - text);
        // Output: Found 'w' at position: 7
    }
    
    // strstr: find substring
    pos = strstr(text, "C programming");
    if (pos) {
        printf("Found substring at position: %ld\n", pos - text);
        // Output: Found substring at position: 22
    }
    
    // strtok: split string by delimiters
    char data[] = "apple,banana,cherry,date";
    char *token = strtok(data, ",");
    
    printf("Tokens:\n");
    while (token != NULL) {
        printf("  %s\n", token);
        token = strtok(NULL, ",");
    }
    
    return 0;
}

Expected output:

Found 'w' at position: 7
Found substring at position: 22
Tokens:
  apple
  banana
  cherry
  date

Formatting Strings

#include <stdio.h>
#include <string.h>

int main() {
    char buffer[100];
    char name[] = "Alice";
    int age = 30;
    double salary = 75000.50;
    
    // sprintf: format into string (DANGEROUS without size limit)
    sprintf(buffer, "%s is %d years old and earns $%.2f", name, age, salary);
    printf("%s\n", buffer);
    // Alice is 30 years old and earns $75000.50
    
    // snprintf: safe version with size limit
    char safe[30];
    int written = snprintf(safe, sizeof(safe), "%s is %d", name, age);
    printf("Safe: '%s' (wrote %d chars)\n", safe, written);
    // Safe: 'Alice is 30' (wrote 12 chars)
    
    // sscanf: parse from string
    int parsed_age;
    char parsed_name[50];
    sscanf(buffer, "%49s is %d years", parsed_name, &parsed_age);
    printf("Parsed: name=%s, age=%d\n", parsed_name, parsed_age);
    // Parsed: name=Alice, age=30
    
    return 0;
}

String Comparison with strcmp

The strcmp function compares strings lexicographically (dictionary order):

#include <stdio.h>
#include <string.h>

int main() {
    // Returns negative if first < second
    printf("'a' vs 'b': %d\n", strcmp("a", "b"));     // < 0
    
    // Returns 0 if equal
    printf("'abc' vs 'abc': %d\n", strcmp("abc", "abc"));  // 0
    
    // Returns positive if first > second
    printf("'z' vs 'a': %d\n", strcmp("z", "a"));     // > 0
    
    // Case matters: uppercase < lowercase
    printf("'A' vs 'a': %d\n", strcmp("A", "a"));     // < 0
    
    // strcasecmp (POSIX) or stricmp (Windows) for case-insensitive
    // On Linux: strcasecmp("hello", "HELLO") returns 0
    
    return 0;
}

Common Mistakes

1. Buffer Overflow

char buf[5];
strcpy(buf, "Hello World");  // Writes past end of buf!

Always use strncpy, snprintf, or ensure the buffer is large enough.

2. Forgetting the Null Terminator

char str[5] = {'H', 'e', 'l', 'l', 'o'};  // Not null-terminated!
printf("%s", str);  // Prints "Hello" plus garbage until '\0'

Always leave room for the null terminator: char str[6] = "Hello";.

3. Using gets()

char buf[100];
gets(buf);  // No bounds check -- never use!

Use fgets(buf, sizeof(buf), stdin) instead.

4. Confusing strlen with sizeof

char str[] = "Hello";
strlen(str)  // 5 -- character count without '\0'
sizeof(str)  // 6 -- includes '\0'

5. Modifying String Literals

char *str = "Hello";
str[0] = 'h';  // Undefined behavior!

Use char str[] = "Hello" to create a modifiable copy.

6. Not Checking strtok Modifies the Original

char *str = "apple,banana";
strtok(str, ",");  // ERROR: string literal is read-only

Copy the string first: char copy[] = "apple,banana"; strtok(copy, ",");

Practice Questions

  1. What marks the end of a C string? The null terminator character \0 (ASCII value 0).

  2. What is the difference between strlen and sizeof on a char array? strlen returns the number of characters before \0. sizeof returns the total allocated size including \0.

  3. Why is gets() dangerous and what should you use instead? gets() has no bounds checking and will overflow any buffer. Use fgets() which accepts a maximum size.

  4. What does strcmp return when strings are equal? 0. A negative value means the first string is lexicographically smaller; positive means larger.

  5. Challenge: Write a program that counts the number of words in a string using strtok.

Mini Project: String Reverser

#include <stdio.h>
#include <string.h>

void reverse(char str[]) {
    int len = strlen(str);
    for (int i = 0; i < len / 2; i++) {
        char temp = str[i];
        str[i] = str[len - 1 - i];
        str[len - 1 - i] = temp;
    }
}

int main() {
    char text[] = "Hello, World!";
    
    printf("Original: %s\n", text);
    reverse(text);
    printf("Reversed: %s\n", text);
    
    // Palindrome check
    char word[] = "racecar";
    char copy[20];
    strcpy(copy, word);
    reverse(copy);
    
    if (strcmp(word, copy) == 0) {
        printf("'%s' is a palindrome.\n", word);
    } else {
        printf("'%s' is not a palindrome.\n", word);
    }
    
    return 0;
}

FAQ

What is a null-terminated string?

A char array where the last character before any non-string data is '\0' (null). The null terminator signals where the string ends.

How do I compare two strings?

Use strcmp() from string.h: if (strcmp(a, b) == 0). Do NOT use ==, which compares pointers, not content.

What is the difference between char str[] and char *str?

char str[] allocates a modifiable array. char *str points to a string literal in read-only memory. You can modify str[] but not *str.

How do I safely copy a string?

Use strncpy(dest, src, size) and ensure null termination: dest[size-1] = '\0'. Or use snprintf for formatting.

Can I concatenate strings without strcat?

Yes. Calculate the position: int len = strlen(dest); strcpy(dest + len, src); Or use snprintf(dest, size, '%s%s', dest, src).

What is Next

Now that you understand strings, proceed to Pointers Basics to learn about memory addresses, dereferencing, and pointer types.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro

Home Browse C