Skip to content
VLSI Mentor

C Part I · Module 12 · Standard Library, Files and Program Interfaces

Memory, String and Utility Functions Worth Knowing

Reading contracts has mostly been a defensive exercise so far — what a function assumes, how it fails, what it will not promise. There is a cheerier use for the same skill. The library contains a small number of functions that do things you would otherwise write yourself — searching, sorting, copying a block of bytes, comparing two — correctly, in code examined by more people than will ever read yours, and usually faster than the obvious loop. Each also comes with preconditions that the library does not check and cannot check, which is what separates replacing your loop from introducing a bug. This chapter is that handful, read the way this module has taught: not what each one does, which is easy, but what each one assumes.

Intermediate19 min readCPart IFundamentalsStandard Library

Part I · Module 12 · Chapter 12.9 · Memory, String and Utility Functions Worth Knowing

1. Why Reach for These at All

Three reasons, in order of how often they are the real one.

Correctness. memmove handles overlapping regions in both directions. A loop you write will handle one direction and silently corrupt the other, and you will test the direction you thought of.

Clarity. memset(buf, 0, sizeof buf) says "clear this" in one line a reader recognises instantly. A loop says "there is a loop here" and makes them read it to find out what kind.

Speed, last and least often decisive. These functions are frequently compiled to specialised instructions, so they can be several times faster than a byte-at-a-time loop — which matters when the data is large and does not matter at all when it is a 12-byte structure.

What none of them buys you is safety. Every function here has a precondition it does not check, and Chapter 12.1 §5 established what that means: violate it and the behaviour is undefined, with no diagnostic.

2. memcpy and memmove

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    #include <string.h>
    void *memcpy (void *dst, const void *src, size_t n);
    void *memmove(void *dst, const void *src, size_t n);

Both copy n bytes. Both return dst, which is convention D — neither can fail. The entire difference is one precondition:

overlap permitted?
memcpyno — the regions must not overlap, or the behaviour is undefined
memmoveyes — the result is as if the source were copied to a temporary first

memmove handles both directions:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
start:                      abcdefgh...
after memmove(a+2, a, 6):   ababcdef...
after memmove(a, a+2, 6):   cdefghgh...

Shifting right and shifting left both produced the correct result. A hand-written forward loop gets the second of those right and the first wrong — it overwrites bytes it has not read yet — which is exactly the bug memmove exists to not have.

And the precondition on memcpy is not diagnosed:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    char b[16] = "abcdefgh";
    memcpy(b + 2, b, 6);              /* overlapping — undefined */
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ cc -std=c17 -Wall -Wextra -Wpedantic -c u2.c -o /dev/null
$

Nothing. Even with both pointers plainly derived from the same local array, at full warning level. That is not a compiler shortcoming — in general the overlap depends on run-time values — but it does mean the check is entirely yours.

Use memmove unless you can state why the regions cannot overlap. The cost is typically a single branch; the cost of being wrong is silent corruption. memcpy is right when the destination is fresh storage you just allocated, or a different object entirely, and those are easy to recognise.

3. memset, and What It Actually Sets

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    void *memset(void *dst, int c, size_t n);

It sets n bytes to (unsigned char)c. Not n elements, and not to the value c:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
memset(int[4], 1) -> 16843009 16843009 16843009 16843009
memset(int[4], 0) -> 0 0 0 0
  0x01010101 each after the 1-fill

16843009 is 0x01010101 — every byte of each four-byte int set to 1. The array was not filled with ones; it was filled with the byte 1, which spells a very different number.

memset is only ever "fill with a value" for char arrays, and for zero.

Zero works because all-bytes-zero is the integer 0 — which is Chapter 9.12 §6's point, and it is why memset(v, 0, sizeof v) is a legitimate idiom while memset(v, 1, sizeof v) is almost always a bug. Note the two caveats Chapter 9.12 §6 attached: all-bytes-zero is not guaranteed to be a null pointer or 0.0, so zeroing an array of pointers this way is not portable.

Two more things to get right:

The size is in bytes. memset(v, 0, sizeof v) for an array in scope; memset(v, 0, n * sizeof *v) for a pointer and a count — Chapter 9.6 §4's idiom, and Chapter 10.6 §5 measured what happens when sizeof is applied to a decayed array.

It cannot fail, convention D, so there is no return value to check — and the entire risk is the size you passed.

4. memcmp, and Where It Is Wrong

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    int memcmp(const void *a, const void *b, size_t n);

Compares n bytes and returns negative, zero or positive — Chapter 8.3's sign contract, which strcmp shares:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
memcmp("abc","abd",3) = -1
strcmp("abc","abd")   = -1

Two places it is the wrong tool, both already measured elsewhere in this curriculum:

Comparing structures. Chapter 10.5 §7 measured two structures with identical member values and different bytes, because padding contents are unspecified. memcmp on those reports them different, correctly and uselessly. Compare member by member.

Comparing strings of different lengths. memcmp needs a length and does not stop at a terminator, so you must supply a length that is valid for both operands. strcmp is the one that knows where strings end.

memcmp is right when you have two blocks of known equal size whose every byte is meaningful — a fixed-size key, a hash, a buffer you filled yourself.

5. qsort and bsearch

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    #include <stdlib.h>
    void  qsort  (void *base, size_t n, size_t size,
                  int (*cmp)(const void *, const void *));
    void *bsearch(const void *key, const void *base, size_t n, size_t size,
                  int (*cmp)(const void *, const void *));

These are Chapter 7.13's function pointers doing real work, and Chapter 10.6's table of records being operated on generically.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    struct reading { int id; int value; };

    static int by_id(const void *a, const void *b)
    {
        const struct reading *x = a, *y = b;
        return (x->id > y->id) - (x->id < y->id);
    }

    qsort(t, n, sizeof t[0], by_id);
    struct reading key = { 7, 0 };
    struct reading *hit = bsearch(&key, t, n, sizeof t[0], by_id);
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
sorted by id: 1 3 7 9
bsearch for id 7 -> found, value 70, index 2
bsearch for id 42 -> NULL

bsearch returns a pointer to the matching element or NULL — convention A, and Chapter 7.4's null-as-a-deliberate-value, exactly as Chapter 10.6 §4's own find_by_id did.

Four preconditions, and the second is the one that produces mysterious results:

The array must be sorted by the same comparison bsearch is given. An unsorted array does not produce a slow search; it produces a wrong answer.

The comparison must be consistent — if cmp(a,b) is negative then cmp(b,a) must be positive, and the ordering must be transitive. An inconsistent comparator makes qsort's behaviour undefined, not merely its output unsorted.

size must be the element size, which is sizeof base[0] — the same sizeof *p discipline as Chapter 9.6 §4.

qsort is not stable. Equal elements may come out in any order. If you need ties broken predictably, compare a second field.

6. Converting Text to Numbers

Covered in Chapter 12.3 §6 and used in Chapters 12.6 and 12.8; collected here because it belongs with the handful.

FunctionForReports failure
strtol / strtollsigned integersend, *end, and errno == ERANGE
strtoul / strtoullunsigned integersthe same three
strtod / strtoffloating pointthe same three
atoi / atol / atof—nothing at all

Use the strto* family. Never use ato*. atoi("banana") and atoi("0") both return 0, with no way to distinguish them, and overflow is undefined rather than reported.

The three checks, from Chapter 12.3 §6: end == input means nothing converted; *end not at the terminator means trailing characters; errno == ERANGE means out of range — and remember the fourth that strtol cannot report, that the value fits a long and not an int.

7. The Preconditions, Collected

This is the table c.ts asks for. Every entry is something the library does not check and whose violation is undefined.

FunctionIt assumesIf you are wrong
memcpythe regions do not overlap; both valid for n bytesundefined — not diagnosed, measured
memmoveboth valid for n bytesundefined
memsetdst valid for n bytes; you meant bytesundefined, or 16843009 where you wanted 1
memcmpboth valid for n bytes; every byte meaningfula correct answer about the wrong question — padding, 10.5 §7
qsortsize is the element size; the comparator is consistentundefined
bsearchthe array is already sorted by that same comparatora wrong answer, silently
strtolthe string is null-terminatedundefined
strcmp / strlenboth arguments are terminated stringsundefined — 8.5's measured failure
any comparatorthe result's sign is correct and cannot overflowundefined if it subtracts

Three of these have appeared as measurements elsewhere in this curriculum, which is the point: the preconditions are not new rules, they are the rules you already know, restated as obligations at a call site.

8. When Not to Use Them

A short list, because "use the library" is good advice that becomes bad advice when applied without thought.

When the element type is not bytes. memcpy between two int arrays is fine; memset to a non-zero value is not, and neither is memcmp between structures.

When a loop says more. Copying three members of a structure by name is clearer than memcpy and cannot get the size wrong.

When the precondition is hard to establish. If you cannot say why two regions do not overlap, you have not met memcpy's precondition, and the correct response is memmove rather than hoping.

When you need stability or a different ordering. qsort is unstable and takes one comparator; some problems need more.

And never to "clear" something sensitive and assume it stays cleared. A compiler may remove a memset whose result is never read — a dead store — so zeroing a buffer just before freeing it is not a reliable way to erase a secret. The standard's answer is memset_s, which is in optional Annex K and is not available everywhere; this is worth knowing exists rather than relying on.

9. What You Should Be Able to Say Now

  1. These functions buy correctness, clarity and sometimes speed — and never safety. Each has an unchecked precondition.
  2. memcpy forbids overlap; memmove permits it — measured working in both directions.
  3. Measured: an obviously overlapping memcpy drew no diagnostic at full warning level.
  4. Use memmove unless you can state why the regions cannot overlap.
  5. memset sets bytes. Measured: memset(int[4], 1) gave 16843009 = 0x01010101.
  6. Only zero does what people expect, and only because all-bytes-zero is integer 0 — not a null pointer, not 0.0.
  7. memcmp is wrong for structures (padding, 10.5 §7) and for strings of unequal length.
  8. bsearch requires the array to be sorted by the same comparator — otherwise a silent wrong answer.
  9. qsort is not stable, and an inconsistent comparator makes it undefined.
  10. return a - b; in a comparator is undefined behaviour when the difference overflows — measured against INT_MAX, undiagnosed by the compiler, caught by a sanitizer.
  11. (a > b) - (a < b) is the correct form, and qsort only ever wanted the sign.
  12. Use strto*; never ato*.

10. Exercises

Exercise 1 — choose the function. For each, name the one to use and the precondition you must establish first.

  • shift the last 10 bytes of a 16-byte buffer to its start
  • zero a freshly allocated array of 100 ints
  • compare two 16-byte hashes
  • copy a struct reading into a fresh slot in a table
  • compare two struct reading objects for equality

Exercise 2 — predict the fill. Give the first element's value after each.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
int   a[4]; memset(a, 0, sizeof a);
int   b[4]; memset(b, 1, sizeof b);
char  c[4]; memset(c, 'x', sizeof c);
int  *d[4]; memset(d, 0, sizeof d);

For the last one, say what it is intended to do and why that intent is not portable.

Exercise 3 — fix the comparator. Rewrite each correctly and say what is wrong with the original.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
int c1(const void *a, const void *b) { return *(const int *)a - *(const int *)b; }
int c2(const void *a, const void *b) { return *(const double *)a - *(const double *)b; }
int c3(const void *a, const void *b) { return strcmp(a, b) > 0; }

Exercise 4 — the silent wrong answer. A program calls bsearch on a table that was sorted by id using a comparator that sorts by value. Describe what happens, why it is not a crash, and which precondition was violated.

Exercise 5 — justify memmove. Give two situations where memcpy is provably correct, and one where the regions might overlap and you cannot easily tell. Say what you would write in the third case.

Exercise 6 — why no warning. Explain in three sentences why the compiler cannot diagnose an overlapping memcpy in general, using the fact that memcpy takes two pointers and a run-time count.

Exercise 7 — the sizes. Give the exact third argument for each.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
int v[20];                              /* zero all of it            */
int *p = malloc(n * sizeof *p);         /* zero all of it            */
struct reading t[50];                   /* copy element 3 to element 7 */
void f(int a[], size_t n);              /* zero it inside f          */

The last one is the interesting case — say why, and name the chapter.

Exercise 8 — read a contract. Using Chapter 12.1's four questions, write the contract for bsearch, including what it returns when the key is absent and everything it assumes about the array.

11. Summary

  • These functions buy correctness, clarity, sometimes speed — never safety.
  • memcpy forbids overlap, memmove permits it; the overlap was not diagnosed. Prefer memmove unless you can prove otherwise.
  • memset sets bytes — measured 16843009, not 1. Zero is the only value that means what you expect.
  • memcmp is wrong for structures and for unequal-length strings.
  • bsearch needs a sorted array; qsort is unstable and needs a consistent comparator.
  • return a - b; is undefined behaviour when it overflows — no warning, caught only by a sanitizer. Use (a > b) - (a < b).
  • strto*, never ato*.
  • Every precondition here is a rule you already know, restated as an obligation at the call site.

12. Closing Module 12

This module was not a tour of the library, and the thing to carry out of it is not a list of functions. It is a method: four questions — what does it assume, what does it return, how does it report failure, what does it not promise — and the knowledge that the fourth is the one people skip and the one that bites.

Applied nine times, that method kept producing the same shape of discovery. printf's argument checking turned out to be a special case performed on literals, vanishing the moment the format is a variable. scanf's failure leaves the offending input exactly where it was, so the obvious retry loop never advances. A successful write call turned out not to be a successful write, because the bytes were still in a buffer that only fclose would empty. An exit status of 256 reported success. And the single most repeated example of errno in the literature — sqrt setting EDOM — did not set errno at all here, because this implementation reports math errors a different way and the standard provides a macro that says so.

None of those is a trick question. Each is what the contract said all along, and each was invisible to anyone working from an example instead.

Three habits carry forward. Check what the contract says the function does when it fails, because the four conventions are not interchangeable and the dangerous one returns a value that looks correct. Separate "did it fail" from "why did it fail" — the function's own return value answers the first and errno only ever answers the second. And say what went wrong, to stderr, naming the program, the thing and the reason, because a status code is eight bits and everything else your program knows has to be said in words.

What this module did not do is tell you how to find a bug once one exists. Every measurement here came from a program written to demonstrate something, run once, with the answer already known. Real debugging starts from the opposite position: a program that is wrong, no idea why, and output that may itself be a symptom. The next module is about that — what undefined behaviour means for a program you are trying to reason about, the warnings and tools that find classes of fault rather than instances, and the discipline that turns "it crashes sometimes" into a specific line of code.

Standards & specifications

Governing standard
ISO/IEC 9899 (the C language standard)(opens ISO/IEC (JTC 1/SC 22/WG 14) in a new tab)

Defines the C language itself — the type system, expression semantics, object model, storage duration and the behaviour categories a conforming implementation must honour. It is a FAMILY of editions (C89/C90, C99, C11, C17, C23) and this curriculum names the edition wherever a claim depends on one. It does not define any foreign-function interface to a hardware description language, and it does not define platform behaviour: what a memory-mapped access does on real silicon is the implementation's and the device's contract, not this standard's. Working drafts published by WG14 are freely available and are what most engineers read day to day; a draft is not the published standard.

This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.

Where this fits

Part of the C Programming curriculum.