Introduction

C has no separate string type. A string is a sequence of characters terminated by the null character '\0' (value 0), stored in an array of char. Unbounded traversal functions such as strlen need a null terminator within accessible storage. Length-bounded operations may stop at the supplied count without encountering a null, subject to each function's preconditions. memcpy and memmove operate on counted bytes, not necessarily strings. This one convention explains most string behaviour in C, including its classic bugs: a missing terminator, a buffer too small for the terminator, and the difference between the size of an array and the length of the string inside it.

Conventions: C11, LP64, ASCII character codes. Functions are from string.h unless noted.

Prerequisites

Core concepts

Length versus size

For char s[] = "GATE"; the array holds the five characters 'G' 'A' 'T' 'E' '\0'.

  • Length strlen(s) counts characters before the first '\0': 4.
  • Size sizeof s counts bytes of the array: 5.
  • char t[10] = "CS"; has length 2 and size 10; the 8 unused bytes are set to '\0'.
  • char w[8] = "ab\0cd"; has length 2: strlen stops at the first null even though more characters follow.
  • char u[] = {'h', 'i'}; has size 2 and no terminator: it is a character array but not a string, and passing it to strlen or printf("%s") reads past its end (undefined behaviour).
  • The empty string "" has length 0 and size 1.

String literals

A string literal such as "GATE" is a static-duration array of char. Its storage contains every explicit literal character (including embedded nulls), plus the appended terminating null. Without embedded nulls its size equals strlen + 1; for "ab\0cd", literal size is 6 but strlen is 2.

  • Adjacent literals are concatenated at compile time: "con" "cat" "enation" is the single literal "concatenation".
  • A literal is an array, so it can be indexed: "hello"[1] is 'e', and *("hello" + 4) is 'o'.
  • Modifying a string literal is undefined behaviour, even though its type in C is char [N] and not const char [N]. On Linux literals are stored in read-only memory, and a write crashes. Whether two identical literals share storage is unspecified.

char s[] versus char *p

C
char s[] = "GATE";          /* array of 5 chars, a modifiable copy of the literal */
const char *p = "GATE";     /* pointer to the literal itself */
Propertychar s[] = "GATE";const char *p = "GATE";
sizeof5 (the array)8 (the pointer, LP64)
strlen44
s[0] = 'g' / p[0] = 'g'allowed for the writable arraythrough const char*: constraint violation requiring a diagnostic; modifying the literal via a non-const access path: undefined behaviour
s = ... / p = ...does not compile (arrays are not assignable)allowed: p can point elsewhere
Storageautomatic (inside the current frame)the literal has static duration

Writing const char * for pointers to literals lets the compiler catch accidental writes.

Character array versus pointer to a string literal
Figure 1. char s[] = "GATE" as five modifiable cells on the stack ending in a null character, beside const char *p = "GATE": an 8-byte pointer that refers to a separate read-only literal of five bytes.

The main library functions

FunctionEffectReturnsWatch out for
strlen(s)counts characters before '\0'size_tΘ(n)\Theta(n) time; not the array size
strcpy(d, s)copies s including '\0' into ddd must have room for strlen(s) + 1 bytes
strncpy(d, s, n)copies at most n charactersdif s has n or more characters, no '\0' is written; if fewer, pads with '\0' up to n
strcat(d, s)appends s to the end of ddd must hold both strings plus '\0'
strcmp(a, b)compares lexicographically by unsigned char valuesnegative, 0 or positivenot necessarily −1-1, 0, 1
strchr(s, c), strrchr(s, c)first or last occurrence of cpointer or NULL
strstr(h, n)first occurrence of substring n in hpointer or NULL
memcpy(d, s, n)copies n bytesdundefined if the regions overlap
memmove(d, s, n)copies n bytes as if through a temporarydsafe for overlapping regions

How strcmp orders strings. It compares character by character; the first differing character decides, and a string that is a proper prefix of the other is smaller. So "abc" < "abd", "ab" < "abc", "b" > "abc", and "Z" < "a" because in ASCII uppercase letters (65 to 90) come before lowercase ones (97 to 122).

Comparing strings with == compares addresses, not contents. For two arrays char s1[] = "hi", s2[] = "hi";, s1 == s2 is 0 while strcmp(s1, s2) == 0 is 1.

Input and output

  • printf("%s", s) prints characters up to the terminator; %c prints one character.
  • scanf("%s", buf) skips leading white space and reads one word, stopping at the next white space; give a width ("%15s" for a 16-byte buffer) to avoid overflow. On input hello world again, scanf("%15s %15s", w1, w2) stores hello and world.
  • With a valid buffer of capacity at least n and n > 1, fgets(buf, n, stdin) reads at most n-1 characters and keeps a newline if it fits. Check its return value: a successful read null-terminates; on EOF/error, handle the failure before using the buffer as a string.
  • gets was removed in C11 because it cannot limit the input size.

How it works: string functions with pointers

The library functions are short pointer loops. Writing them is a useful exercise.

C
size_t my_strlen(const char *s) {
    const char *p = s;
    while (*p) p++;          /* stop at '\0' */
    return p - s;            /* elements between the two pointers */
}

void my_strcpy(char *d, const char *s) {
    while ((*d++ = *s++))    /* copy, then test the copied char */
        ;                    /* the '\0' is copied before the loop stops */
}

In my_strcpy, each iteration evaluates *d = *s (the value of the assignment is the copied character), advances both pointers, and stops after copying the terminating '\0', whose value 0 ends the loop.

Reversing a string in place swaps s[i] and s[j] while i < j, with i from 0 and j from strlen(s) - 1. The empty string must be handled separately, because strlen(s) - 1 is a huge size_t when the length is 0.

Worked examples

All outputs below are for C11 with gcc on x86-64 Linux (LP64).

Example 1 (sizes and lengths). For char s[] = "GATE"; const char *p = "GATE"; char t[10] = "CS";, the expression list sizeof s, strlen(s), sizeof p, strlen(p), sizeof t gives 5 4 8 4 10.

Example 2 (library calls).

C
char a[20] = "data"; char b[] = "base";
strcat(a, b);                         /* a is "database", length 8 */
char c[20]; strcpy(c, a); c[4] = '\0';/* c is "data" */
char m[] = "abcdef";
memmove(m + 2, m, 3);                 /* m is "ababcf" */

For memmove(m + 2, m, 3) the source abc and destination positions 2 to 4 overlap; memmove copies as if through a buffer, giving a b a b c f. With memcpy the result would be undefined.

Example 3 (strncpy does not always terminate). char d[4]; strncpy(d, "hello", 4); fills d with h e l l and writes no '\0'. char e[6]; strncpy(e, "hi", 6); writes h i followed by four '\0' characters.

Example 4 (searching). In "operating systems", strchr finds 'r' at index 3, strstr finds "sys" at index 10, and strrchr(hay, 's') points to the final "s".

Example 5 (pointer arithmetic on strings). char str[] = "GATE2027"; char *q = str + 4; then printf("%s %c %s", q, *q, str + 6) prints 2027 2 27.

Example 6 (counting characters). Counting vowels in "Programming in C" with strchr("aeiouAEIOU", *s) gives 4 (o, a, i, i).

Important properties and observations

  • A string needs one byte more than its length.
  • sizeof gives array size (or pointer size); strlen gives the length and costs Θ(n)\Theta(n).
  • Calling strlen in a loop condition, for (i = 0; i < strlen(s); i++), makes the loop Θ(n2)\Theta(n^2) unless the compiler hoists the call.
  • String literals must not be modified; use const char * to point at them.
  • strcmp returns a sign, not necessarily ±1\pm 1; compare its result with 0.
  • strncpy may leave the destination unterminated.
  • memcpy on overlapping regions is undefined; use memmove.

GATE examination perspective

  • Output questions with printf("%s", p + k) print from position kk to the terminator.
  • sizeof versus strlen on arrays, pointers, and arrays with embedded nulls.
  • Pointer loops such as while (*s++) or while ((*d++ = *s++)): track where each pointer ends (one past the terminator in the first case).
  • Modifying a literal through char *p = "..." makes the program undefined; the same mistake can appear inside a string-reversal function.
  • Lexicographic comparison rules for strcmp, including case and prefixes.

Summary

Sources

  1. ISO/IEC 9899:2011 (C11), committee draft N1570, (consulted 2026-11-09), Section 6.4.5 (string literals), 6.7.9 (initialization of character arrays), 7.24 (string handling: 7.24.2 copying functions, 7.24.3 concatenation, 7.24.4 comparison, 7.24.5 search, 7.24.6.3 strlen), 7.21.6.2 (fscanf, the %s directive), 7.21.7.2 (fgets); Foreword (removal of gets).
  2. B. W. Kernighan and D. M. Ritchie, The C Programming Language, 2nd ed., Prentice Hall, 1988: Sections 5.5 (character pointers and functions) and Appendix B3 (string functions).

Next steps