BGC Usl C 1
BGC Usl C 1
BGC Usl C 1
1 Foreword 1
1.1 Audience . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.2 How to Read This Book . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.3 Platform and Compiler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.4 Official Homepage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.5 Email Policy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.6 Mirroring . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.7 Note for Translators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.8 Copyright and Distribution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.9 Dedication . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
2 Hello, World! 5
2.1 What to Expect from C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2.2 Hello, World! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
2.3 Compilation Details . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
2.4 Building with gcc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
2.5 Building with clang . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
2.6 Building from IDEs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
2.7 C Versions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
4 Functions 25
4.1 Passing by Value . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
4.2 Function Prototypes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
4.3 Empty Parameter Lists . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
i
CONTENTS ii
5 Pointers—Cower In Fear! 29
5.1 Memory and Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
5.2 Pointer Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
5.3 Dereferencing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
5.4 Passing Pointers as Arguments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
5.5 The NULL Pointer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
5.6 A Note on Declaring Pointers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
5.7 sizeof and Pointers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
6 Arrays 36
6.1 Easy Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
6.2 Getting the Length of an Array . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
6.3 Array Initializers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
6.4 Out of Bounds! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
6.5 Multidimensional Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
6.6 Arrays and Pointers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
6.6.1 Getting a Pointer to an Array . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
6.6.2 Passing Single Dimensional Arrays to Functions . . . . . . . . . . . . . . . . . . . 41
6.6.3 Changing Arrays in Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
6.6.4 Passing Multidimensional Arrays to Functions . . . . . . . . . . . . . . . . . . . 43
7 Strings 45
7.1 String Literals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
7.2 String Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
7.3 String Variables as Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
7.4 String Initializers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
7.5 Getting String Length . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
7.6 String Termination . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
7.7 Copying a String . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
8 Structs 50
8.1 Declaring a Struct . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
8.2 Struct Initializers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
8.3 Passing Structs to Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
8.4 The Arrow Operator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
8.5 Copying and Returning structs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
8.6 Comparing structs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
9 File Input/Output 54
9.1 The FILE* Data Type . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
9.2 Reading Text Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
9.3 End of File: EOF . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
9.3.1 Reading a Line at a Time . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
9.4 Formatted Input . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
9.5 Writing Text Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
9.6 Binary File I/O . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
9.6.1 struct and Number Caveats . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
13 Scope 84
13.1 Block Scope . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
13.1.1 Where To Define Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
13.1.2 Variable Hiding . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
13.2 File Scope . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
13.3 for-loop Scope . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
13.4 A Note on Function Scope . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
31 goto 224
31.1 A Simple Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 224
31.2 Labeled continue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 225
31.3 Bailing Out . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 226
31.4 Labeled break . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 227
31.5 Multi-level Cleanup . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 227
31.6 Tail Call Optimization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 228
31.7 Restarting Interrupted System Calls . . . . . . . . . . . . . . . . . . . . . . . . . . . 229
31.8 goto and Variable Scope . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 230
31.9 goto and Variable-Length Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 231
39 Multithreading 266
39.1 Background . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 266
39.2 Things You Can Do . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 267
39.3 Data Races and the Standard Library . . . . . . . . . . . . . . . . . . . . . . . . . . . . 267
39.4 Creating and Waiting for Threads . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 267
39.5 Detaching Threads . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 271
39.6 Thread Local Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 272
39.6.1 _Thread_local Storage-Class . . . . . . . . . . . . . . . . . . . . . . . . . . . 274
39.6.2 Another Option: Thread-Specific Storage . . . . . . . . . . . . . . . . . . . . . . 274
39.7 Mutexes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 276
39.7.1 Different Mutex Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 279
39.8 Condition Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 280
39.8.1 Timed Condition Wait . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 283
39.8.2 Broadcast: Wake Up All Waiting Threads . . . . . . . . . . . . . . . . . . . . . . 284
39.9 Running a Function One Time . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 284
40 Atomics 285
40.1 Testing for Atomic Support . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 285
40.2 Atomic Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 285
40.3 Synchronization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 287
40.4 Acquire and Release . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 289
40.5 Sequential Consistency . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 290
40.6 Atomic Assignments and Operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 291
CONTENTS ix
Foreword
1.1 Audience
This guide assumes that you’ve already got some programming knowledge under your belt from another
language, such as Python2 , JavaScript3 , Java4 , Rust5 , Go6 , Swift7 , etc. (Objective-C8 devs will have a par-
1
https://www.ioccc.org/
2
https://en.wikipedia.org/wiki/Python_(programming_language)
3
https://en.wikipedia.org/wiki/JavaScript
4
https://en.wikipedia.org/wiki/Java_(programming_language)
5
https://en.wikipedia.org/wiki/Rust_(programming_language)
6
https://en.wikipedia.org/wiki/Go_(programming_language)
7
https://en.wikipedia.org/wiki/Swift_(programming_language)
8
https://en.wikipedia.org/wiki/Objective-C
1
Chapter 1. Foreword 2
As a rule, the more complex the question, the less likely I am to respond. If you can narrow down your
question before mailing it and be sure to include any pertinent information (like platform, compiler, error
messages you’re getting, and anything else you think might help me troubleshoot), you’re much more likely
to get a response.
If you don’t get a response, hack on it some more, try to find the answer, and if it’s still elusive, then write
me again with the information you’ve found and hopefully it will be enough for me to help out.
Now that I’ve badgered you about how to write and not write me, I’d just like to let you know that I fully
appreciate all the praise the guide has received over the years. It’s a real morale boost, and it gladdens me to
hear that it is being used for good! :-) Thank you!
1.6 Mirroring
You are more than welcome to mirror this site, whether publicly or privately. If you publicly mirror the site
and want me to link to it from the main page, drop me a line at beej@beej.us.
1.9 Dedication
The hardest things about writing these guides are:
• Learning the material in enough detail to be able to explain it
• Figuring out the best way to explain it clearly, a seemingly-endless iterative process
• Putting myself out there as a so-called authority, when really I’m just a regular human trying to make
sense of it all, just like everyone else
• Keeping at it when so many other things draw my attention
Chapter 1. Foreword 4
A lot of people have helped me through this process, and I want to acknowledge those who have made this
book possible.
• Everyone on the Internet who decided to help share their knowledge in one form or another. The free
sharing of instructive information is what makes the Internet the great place that it is.
• The volunteers at cppreference.com16 who provide the bridge that leads from the spec to the real world.
• The helpful and knowledgeable folks on comp.lang.c17 and r/C_Programming18 who got me through
the tougher parts of the language.
• Everyone who submitted corrections and pull-requests on everything from misleading instructions to
typos.
Thank you! ♥
16
https://en.cppreference.com/
17
https://groups.google.com/g/comp.lang.c
18
https://www.reddit.com/r/C_Programming/
Chapter 2
Hello, World!
5
Chapter 2. Hello, World! 6
Everything else in C is just memorizing another way (or sometimes the same way!) of doing something
you’ve done already. Pointers are the weird bit. And, arguably, even pointers are variations on a theme
you’re probably familiar with.
So get ready for a rollicking adventure as close to the core of the computer as you can get without assembly,
in the most influential computer language of all time7 . Hang on!
3 #include <stdio.h>
4
5 int main(void)
6 {
7 printf("Hello, World!\n"); // Actually do the work here
8 }
We’re going to don our long-sleeved heavy-duty rubber gloves, grab a scalpel, and rip into this thing to see
what makes it tick. So, scrub up, because here we go. Cutting very gently…
Let’s get the easy thing out of the way: anything between the digraphs /* and */ is a comment and will be
completely ignored by the compiler. Same goes for anything on a line after a //. This allows you to leave
messages to yourself and others, so that when you come back and read your code in the distant future, you’ll
know what the heck it was you were trying to do. Believe me, you will forget; it happens.
Now, what is this #include? GROSS! Well, it tells the C Preprocessor to pull the contents of another file
and insert it into the code right there.
Wait—what’s a C Preprocessor? Good question. There are two stages8 to compilation: the preprocessor
and the compiler. Anything that starts with pound sign, or “octothorpe”, (#) is something the preprocessor
operates on before the compiler even gets started. Common preprocessor directives, as they’re called, are
#include and #define. More on that later.
Before we go on, why would I even begin to bother pointing out that a pound sign is called an octothorpe?
The answer is simple: I think the word octothorpe is so excellently funny, I have to gratuitously spread its
name around whenever I get the opportunity. Octothorpe. Octothorpe, octothorpe, octothorpe.
So anyway. After the C preprocessor has finished preprocessing everything, the results are ready for the
compiler to take them and produce assembly code9 , machine code10 , or whatever it’s about to do. Machine
code is the “language” the CPU understands, and it can understand it very rapidly. This is one of the reasons
C programs tend to be quick.
Don’t worry about the technical details of compilation for now; just know that your source runs through the
preprocessor, then the output of that runs through the compiler, then that produces an executable for you to
run.
What about the rest of the line? What’s <stdio.h>? That is what is known as a header file. It’s the dot-h at
the end that gives it away. In fact it’s the “Standard I/O” (stdio) header file that you will grow to know and
love. It gives us access to a bunch of I/O functionality11 . For our demo program, we’re outputting the string
7
I know someone will fight me on that, but it’s gotta be at least in the top three, right?
8
Well, technically there are more than two, but hey, let’s pretend there are two—ignorance is bliss, right?
9
https://en.wikipedia.org/wiki/Assembly_language
10
https://en.wikipedia.org/wiki/Machine_code
11
Technically, it contains preprocessor directives and function prototypes (more on that later) for common input and output needs.
Chapter 2. Hello, World! 7
“Hello, World!”, so we in particular need access to the printf() function to do this. The <stdio.h> file
gives us this access. Basically, if we tried to use without #include <stdio.h>, the compiler would have
complained to us about it.
How did I know I needed to #include <stdio.h> for printf()? Answer: it’s in the documentation. If
you’re on a Unix system, man 3 printf and it’ll tell you right at the top of the man page what header files
are required. Or see the reference section in this book. :-)
Holy moly. That was all to cover the first line! But, let’s face it, it has been completely dissected. No mystery
shall remain!
So take a breather…look back over the sample code. Only a couple easy lines to go.
Welcome back from your break! I know you didn’t really take a break; I was just humoring you.
The next line is main(). This is the definition of the function main(); everything between the squirrelly
braces ({ and }) is part of the function definition.
(How do you call a different function, anyway? The answer lies in the printf() line, but we’ll get to that
in a minute.)
Now, the main function is a special one in many ways, but one way stands above the rest: it is the function
that will be called automatically when your program starts executing. Nothing of yours gets called before
main(). In the case of our example, this works fine since all we want to do is print a line and exit.
Oh, that’s another thing: once the program executes past the end of main(), down there at the closing
squirrelly brace, the program will exit, and you’ll be back at your command prompt.
So now we know that that program has brought in a header file, stdio.h, and declared a main() function
that will execute when the program is started. What are the goodies in main()?
I am so happy you asked. Really! We only have the one goodie: a call to the function printf(). You can
tell this is a function call and not a function definition in a number of ways, but one indicator is the lack of
squirrelly braces after it. And you end the function call with a semicolon so the compiler knows it’s the end
of the expression. You’ll be putting semicolons after almost everything, as you’ll see.
You’re passing one argument to the function printf(): a string to be printed when you call it. Oh, yeah—
we’re calling a function! We rock! Wait, wait—don’t get cocky. What’s that crazy \n at the end of the string?
Well, most characters in the string will print out just like they are stored. But there are certain characters that
you can’t print on screen well that are embedded as two-character backslash codes. One of the most popular
is \n (read “backslash-N”) that corresponds to the newline character. This is the character that causes further
printing to continue at the beginning of the next line instead of the current. It’s like hitting return at the end
of the line.
So copy that code into a file called hello.c and build it. On a Unix-like platform (e.g. Linux, BSD, Mac,
or WSL), from the command line you’ll build with a command like so:
gcc -o hello hello.c
(The leading ./ tells the shell to “run from the current directory”.)
And see what happens:
Hello, World!
The -o means “output to this file”13 . And there’s hello.c at the end, the name of the file we want to compile.
If your source is broken up into multiple files, you can compile them all together (almost as if they were one
file, but the rules are actually more complex than that) by putting all the .c files on the command line:
gcc -o awesomegame ui.c characters.c npc.c items.c
2.7 C Versions
C has come a long way over the years, and it had many named version numbers to describe which dialect of
the language you’re using.
These generally refer to the year of the specification.
The most famous are C89, C99, C11, and C2x. We’ll focus on the latter in this book.
But here’s a more complete table:
Version Description
K&R C 1978, the original. Named after Brian Kernighan and Dennis Ritchie.
Ritchie designed and coded the language, and Kernighan co-authored the
book on it. You rarely see original K&R code today. If you do, it’ll look odd,
like Middle English looks odd to modern English readers.
C89, ANSI C, C90 In 1989, the American National Standards Institute (ANSI) produced a C
language specification that set the tone for C that persists to this day. A year
later, the reins were handed to the International Organization for
Standardization (ISO) that produced the identical C90.
C95 A rarely-mentioned addition to C89 that included wide character support.
C99 The first big overhaul with lots of language additions. The thing most people
remember is the addition of //-style comments. This is the most popular
version of C in use as of this writing.
C11 This major version update includes Unicode support and multi-threading. Be
advised that if you start using these language features, you might be
sacrificing portability with places that are stuck in C99 land. But, honestly,
1999 is getting to be a while back now.
C17, C18 Bugfix update to C11. C17 seems to be the official name, but the publication
was delayed until 2018. As far as I can tell, these two are interchangeable,
with C17 being preferred.
C2x What’s coming next! Expected to eventually become C21.
You can force GCC to use one of these standards with the -std= command line argument. If you want it to
be picky about the standard, add -pedantic.
For example:
gcc -std=c11 -pedantic foo.c
For this book, I compile programs for C2x with all warnings set:
Chapter 2. Hello, World! 10
3.1 Variables
It’s said that “variables hold values”. But another way to think about it is that a variable is a human-readable
name that refers to some data in memory.
We’re going to take a second here and take a peek down the rabbit hole that is pointers. Don’t worry about
it.
You can think of memory as a big array of bytes1 . Data is stored in this “array”2 . If a number is larger than
a single byte, it is stored in multiple bytes. Because memory is like an array, each byte of memory can be
referred to by its index. This index into memory is also called an address, or a location, or a pointer.
When you have a variable in C, the value of that variable is in memory somewhere, at some address. Of
course. After all, where else would it be? But it’s a pain to refer to a value by its numeric address, so we
make a name for it instead, and that’s what the variable is.
The reason I’m bringing all this up is twofold:
1. It’s going to make it easier to understand pointer variables later—they’re variables that hold the address
of other variables!
2. Also, it’s going to make it easier to understand pointers later.
So a variable is a name for some data that’s stored in memory at some address.
1
A “byte” is typically an 8-bit binary number. Think of it as an integer that can only hold the values from 0 to 255, inclusive.
Technically, C allows bytes to be any number of bits and if you want to unambiguously refer to an 8-bit number, you should use the
term octet. But programmers are going assume you mean 8-bits when you say “byte” unless you specify otherwise.
2
I’m seriously oversimplifying how modern memory works, here. But the mental model works, so please forgive me.
11
Chapter 3. Variables and Statements 12
C makes an effort to convert automatically between most numeric types when you ask it to. But other than
that, all conversions are manual, notably between string and numeric.
Almost all of the types in C are variants on these types.
Before you can use a variable, you have to declare that variable and tell C what type the variable holds. Once
declared, the type of variable cannot be changed later at runtime. What you set it to is what it is until it falls
out of scope and is reabsorbed into the universe.
Let’s take our previous “Hello, world” code and add a couple variables to it:
1 #include <stdio.h>
2
3 int main(void)
4 {
5 int i; // Holds signed integers, e.g. -3, -2, 0, 1, 10
6 float f; // Holds signed floating point numbers, e.g. -3.1416
7
There! We’ve declared a couple of variables. We haven’t used them yet, and they’re both uninitialized. One
holds an integer number, and the other holds a floating point number (a real number, basically, if you have a
math background).
Uninitialized variables have indeterminate value4 . They have to be initialized or else you must assume they
contain some nonsense number.
3
Read this as “pointer to a char” or “char pointer”. “Char” for character. Though I can’t find a study, it seems anecdotally most
people pronounce this as “char”, a minority say “car”, and a handful say “care”. We’ll talk more about pointers later.
4
Colloquially, we say they have “random” values, but they aren’t truly—or even pseudo-truly—random numbers.
Chapter 3. Variables and Statements 13
This is one of the places C can “get you”. Much of the time, in my experience, the indeterminate
value is zero… but it can vary from run to run! Never assume the value will be zero, even if you
see it is. Always explicitly initialize variables to some value before you use them5 .
What’s this? You want to store some numbers in those variables? Insanity!
Let’s go ahead and do that:
1 int main(void)
2 {
3 int i;
4
7 printf("Hello, World!\n");
8 }
3 int main(void)
4 {
5 int i = 2;
6 float f = 3.14;
7 char *s = "Hello, world!"; // char * ("char pointer") is the string type
8
In this way, printf() might be similar to various types of format strings or parameterized strings in other
languages you’re familiar with.
Historically, C didn’t have a Boolean type, and some might argue it still doesn’t.
In C, 0 means “false”, and non-zero means “true”.
So 1 is true. And -37 is true. And 0 is false.
5
This isn’t strictly 100% true. When we get to learning about static storage duration, you’ll find the some variables are initialized to
zero automatically. But the safe thing to do is always initialize them.
Chapter 3. Variables and Statements 14
if (x) {
printf("x is true!\n");
}
If you #include <stdbool.h>, you also get access to some symbolic names that might make things look
more familiar, namely a bool type and true and false values:
1 #include <stdio.h>
2 #include <stdbool.h>
3
4 int main(void) {
5 bool x = true;
6
7 if (x) {
8 printf("x is true!\n");
9 }
10 }
But these are identical to using integer values for true and false. They’re just a facade to make things look
nice.
3.2.1 Arithmetic
Hopefully these are familiar:
i = i + 3; // Addition (+) and assignment (=) operators, add 3 to i
i = i - 8; // Subtraction, subtract 8 from i
i = i * 9; // Multiplication
i = i / 2; // Division
i = i % 5; // Modulo (division remainder)
There are shorthand variants for all of the above. Each of those lines could more tersely be written as:
i += 3; // Same as "i = i + 3", add 3 to i
i -= 8; // Same as "i = i - 8"
i *= 9; // Same as "i = i * 9"
i /= 2; // Same as "i = i / 2"
i %= 5; // Same as "i = i % 5"
There is no exponentiation. You’ll have to use one of the pow() function variants from math.h.
Let’s get into some of the weirder stuff you might not have in your other languages!
What a mess! You’ll get used to it the more you read it. To help out a bit, I’ll rewrite the above expression
using if statements:
// This expression:
if (x > 10)
y += 17;
else
y += 37;
Compare those two until you see each of the components of the ternary operator.
Or, another example that prints if a number stored in x is odd or even:
printf("The number %d is %s.\n", x, x % 2 == 0? "even": "odd")
The %s format specifier in printf() means print a string. If the expression x % 2 evaluates to 0, the value
of the entire ternary expression evaluates to the string "even". Otherwise it evaluates to the string "odd".
Pretty cool!
It’s important to note that the ternary operator isn’t flow control like the if statement is. It’s just an expression
that evaluates to a value.
but they’re more subtly different than that, the clever scoundrels.
Let’s take a look at this variant, pre-increment and pre-decrement:
++i; // Add one to i (pre-increment)
--i; // Subtract one from i (pre-decrement)
With pre-increment and pre-decrement, the value of the variable is incremented or decremented before the
expression is evaluated. Then the expression is evaluated with the new value.
With post-increment and post-decrement, the value of the expression is first computed with the value as-is,
and then the value is incremented or decremented after the value of the expression has been determined.
You can actually embed them in expressions, like this:
Chapter 3. Variables and Statements 16
i = 10;
j = 5 + i++; // Compute 5 + i, _then_ increment i
This technique is used frequently with array and pointer access and manipulation. It gives you a way to use
the value in a variable, and also increment or decrement that value before or after it is used.
But by far the most common place you’ll see this is in a for loop:
for (i = 0; i < 10; i++)
printf("i is %d\n", i);
Seems a bit silly, since you could just replace the comma with a semicolon, right?
x = 10; y = 20; // First assign 10 to x, then 20 to y
But that’s a little different. The latter is two separate expressions, while the former is a single expression!
With the comma operator, the value of the comma expression is the value of the rightmost expression:
x = (1, 2, 3);
But even that’s pretty contrived. One common place the comma operator is used is in for loops to do multiple
things in each section of the statement:
for (i = 0, j = 10; i < 100; i++, j++)
printf("%d, %d\n", i, j);
Don’t mix up assignment = with comparison ==! Use two equals to compare, one to assign.
Chapter 3. Variables and Statements 17
! has higher precedence than the other Boolean operators, so we have to use parentheses in that case.
int a = 999;
Remember: it’s the size in bytes of the type of the expression, not the size of the expression itself. That’s
why the size of 2+7 is the same as the size of a—they’re both type int. We’ll revisit this number 4 in the
very next block of code…
…Where we’ll see you can take the sizeof a type (note the parentheses are required around a type name,
unlike an expression):
printf("%zu\n", sizeof(int)); // Prints 4 on my system
printf("%zu\n", sizeof(char)); // Prints 1 on all systems
It’s important to note that sizeof is a compile-time operation7 . The result of the expression is determined
entirely at compile-time, not at runtime.
We’ll make use of this later on.
This is also sometimes written on a separate line. (Whitespace is largely irrelevant in C—it’s not like Python.)
if (x == 10)
printf("x is 10\n");
But what if you want multiple things to happen due to the conditional? You can use squirrelly braces to mark
a block or compound statement.
if (x == 10) {
printf("x is 10\n");
printf("And also this happens when x is 10\n");
}
It’s a really common style to always use squirrelly braces even if they aren’t necessary:
if (x == 10) {
printf("x is 10\n");
}
7
Except for with variable length arrays—but that’s a story for another time.
Chapter 3. Variables and Statements 19
Some devs feel the code is easier to read and avoids errors like this where things visually look like they’re
in the if block, but actually they aren’t.
// BAD ERROR EXAMPLE
if (x == 10)
printf("This happens if x is 10\n");
printf("This happens ALWAYS\n"); // Surprise!! Unconditional!
while and for and the other looping constructs work the same way as the examples above. If you want to
do multiple things in a loop or after an if, wrap them up in squirrelly braces.
In other words, the if is going to run the one thing after the if. And that one thing can be a single statement
or a block of statements.
if (i > 10) {
printf("Yes, i is greater than 10.\n");
printf("And this will also print if i is greater than 10.\n");
}
In the example code, the message will print if i is greater than 10, otherwise execution continues to the next
line. Notice the squirrley braces after the if statement; if the condition is true, either the first statement or
expression right after the if will be executed, or else the collection of code in the squirlley braces after the
if will be executed. This sort of code block behavior is common to all statements.
Of course, because C is fun this way, you can also do something if the condition is false with an else clause
on your if:
int i = 99;
if (i == 10)
printf("i is 10!\n");
else {
printf("i is decidedly not 10.\n");
printf("Which irritates me a little, frankly.\n");
}
And you can even cascade these to test a variety of conditions, like this:
int i = 99;
if (i == 10)
printf("i is 10!\n");
else if (i == 20)
printf("i is 20!\n");
else if (i == 99) {
printf("i is 99! My favorite\n");
Chapter 3. Variables and Statements 20
else
printf("i is some crazy number I've never heard of.\n");
Though if you’re going that route, be sure to check out the switch statement for a potentially better solution.
The catch is switch only works with equality comparisons with constant numbers. The above if-else
cascade could check inequality, ranges, variables, or anything else you can craft in a conditional expression.
Let’s do one!
// Print the following output:
//
// i is now 0!
// i is now 1!
// [ more of the same between 2 and 7 ]
// i is now 8!
// i is now 9!
i = 0;
printf("All done!\n");
That gets you a basic loop. C also has a for loop which would have been cleaner for that example.
A not-uncommon use of while is for infinite loops where you repeat while true:
while (1) {
printf("1 is always true, so this repeats forever.\n");
}
They are basically the same, except if the loop condition is false on the first pass, do-while will execute
once, but while won’t execute at all. In other words, the test to see whether or not to execute the block
happens at the end of the block with do-while. It happens at the beginning of the block with while.
Let’s see by example:
// Using a while statement:
i = 10;
Chapter 3. Variables and Statements 21
i = 10;
// this is executed once, because the loop condition is not checked until
// after the body of the loop runs:
do {
printf("do-while: i is %d\n", i);
i++;
} while (i < 10);
printf("All done!\n");
Notice that in both cases, the loop condition is false right away. So in the while, the loop fails, and the
following block of code is never executed. With the do-while, however, the condition is checked after the
block of code executes, so it always executes at least once. In this case, it prints the message, increments i,
then fails the condition, and continues to the “All done!” output.
The moral of the story is this: if you want the loop to execute at least once, no matter what the loop condition,
use do-while.
All these examples might have been better done with a for loop. Let’s do something less deterministic—
repeat until a certain random number comes up!
1 #include <stdio.h> // For printf
2 #include <stdlib.h> // For rand
3
4 int main(void)
5 {
6 int r;
7
8 do {
9 r = rand() % 100; // Get a random number between 0 and 99
10 printf("%d\n", r);
11 } while (r != 37); // Repeat until 37 comes up
12 }
Side note: did you run that more than once? If you did, did you notice the same sequence of numbers came
up again. And again. And again? This is because rand() is a pseudorandom number generator that must
be seeded with a different number in order to generate a different sequence. Look up the srand()8 function
for more details.
You could do the same thing using just a while loop, but the for loop can help keep the code cleaner.
Here are two pieces of equivalent code—note how the for loop is just a more compact representation:
// Print numbers between 0 and 9, inclusive...
i = 0;
while (i < 10) {
printf("i is %d\n", i);
i++;
}
That’s right, folks—they do exactly the same thing. But you can see how the for statement is a little more
compact and easy on the eyes. (JavaScript users will fully appreciate its C origins at this point.)
It’s split into three parts, separated by semicolons. The first is the initialization, the second is the loop
condition, and the third is what should happen at the end of the block if the loop condition is true. All three
of these parts are optional.
for (initialize things; loop if this is true; do this after each loop)
Note that the loop will not execute even a single time if the loop condition starts off false.
for-loop fun fact!
You can use the comma operator to do multiple things in each clause of the for loop!
for (i = 0, j = 999; i < 10; i++, j--) {
printf("%d, %d\n", i, j);
}
Let’s do an example where the user enters a number of goats and we print out a gut-feel of how many goats
that is.
Chapter 3. Variables and Statements 23
1 #include <stdio.h>
2
3 int main(void)
4 {
5 int goat_count;
6
10 switch (goat_count) {
11 case 0:
12 printf("You have no goats.\n");
13 break;
14
15 case 1:
16 printf("You have a singular goat.\n");
17 break;
18
19 case 2:
20 printf("You have a brace of goats.\n");
21 break;
22
23 default:
24 printf("You have a bona fide plethora of goats!\n");
25 break;
26 }
27 }
In that example, if the user enters, say, 2, the switch will jump to the case 2 and execute from there. When
(if) it hits a break, it jumps out of the switch.
Also, you might see that default label there at the bottom. This is what happens when no cases match.
Every case, including default, is optional. And they can occur in any order, but it’s really typical for
default, if any, to be listed last.
Turns out we just keep on going into the next case! Demo!
switch (x) {
case 1:
printf("1\n");
// Fall through!
case 2:
printf("2\n");
break;
case 3:
printf("3\n");
break;
}
If x == 1, this switch will first hit case 1, it’ll print the 1, but then it just continues on to the next line of
code… which prints 2!
And then, at last, we hit a break so we jump out of the switch.
if x == 2, then we just hit the case 2, print 2, and break as normal.
Not having a break is called fall through.
ProTip: ALWAYS put a comment in the code where you intend to fall through, like I did above. It will save
other programmers from wondering if you meant to do that.
In fact, this is one of the common places to introduce bugs in C programs: forgetting to put a break in your
case. You gotta do it if you don’t want to just roll into the next case9 .
Earlier I said that switch works with integer types—keep it that way. Don’t use floating point or string types
in there. One loophole-ish thing here is that you can use character types because those are secretly integers
themselves. So this is perfectly acceptable:
char c = 'b';
switch (c) {
case 'a':
printf("It's 'a'!\n");
break;
case 'b':
printf("It's 'b'!\n");
break;
case 'c':
printf("It's 'c'!\n");
break;
}
Finally, you can use enums in switch since they are also integer types. But more on that in the enum chapter.
9
This was considered such a hazard that the designers of the Go Programming Language made break the default; you have to
explicitly use Go’s fallthrough statement if you want to fall into the next case.
Chapter 4
Functions
“Sir, not in an environment such as this. That’s why I’ve also been programmed for over thirty
secondary functions that—”
—C3PO, before being rudely interrupted, reporting a now-unimpressive number of additional
functions, Star Wars script
Very much like other languages you’re used to, C has the concept of functions.
Functions can accept a variety of arguments and return a value. One important thing, though: the arguments
and return value types are predeclared—because that’s how C likes it!
Let’s take a look at a function. This is a function that takes an int as an argument, and returns an int.
1 #include <stdio.h>
2
25
Chapter 4. Functions 26
Before I forget, notice that I defined the function before I used it. If I hadn’t done that, the
compiler wouldn’t know about it yet when it compiles main() and it would have given an
unknown function call error. There is a more proper way to do the above code with function
prototypes, but we’ll talk about that later.
Also notice that main() is a function!
It returns an int.
But what’s this void thing? This is a keyword that’s used to indicate that the function accepts no arguments.
You can also return void to indicate that you don’t return a value:
1 #include <stdio.h>
2
5 void hello(void)
6 {
7 printf("Hello, world!\n");
8 }
9
10 int main(void)
11 {
12 hello(); // Prints "Hello, world!"
13 }
3 void increment(int a)
4 {
5 a++;
6 }
7
8 int main(void)
9 {
10 int i = 10;
11
12 increment(i);
13
At first glance, it looks like i is 10, and we pass it to the function increment(). There the value gets
incremented, so when we print it, it must be 11, right?
“Get used to disappointment.”
—Dread Pirate Roberts, The Princess Bride
But it’s not 11—it prints 10! How?
It’s all about the fact that the expressions you pass to functions get copied onto their corresponding parameters.
The parameter is a copy, not the original.
So i is 10 out in main(). And we pass it to increment(). The corresponding parameter is called a in that
function.
And the copy happens, as if by assignment. Loosely, a = i. So at that point, a is 10. And out in main(), i
is also 10.
Then we increment a to 11. But we’re not touching i at all! It remains 10.
Finally, the function is complete. All its local variables are discarded (bye, a!) and we return to main(),
where i is still 10.
And we print it, getting 10, and we’re done.
This is why in the previous example with the plus_one() function, we returned the locally modified value
so that we could see it again in main().
Seems a little bit restrictive, huh? Like you can only get one piece of data back from a function, is what
you’re thinking. There is, however, another way to get data back; C folks call it passing by reference and
that’s a story we’ll tell another time.
But no fancy-schmancy name will distract you from the fact that EVERYTHING you pass to a function WITH-
OUT EXCEPTION is copied into its corresponding parameter, and the function operates on that local copy,
NO MATTER WHAT. Remember that, even when we’re talking about this so-called passing by reference.
5 int main(void)
6 {
7 int i;
8
12 i = foo();
Chapter 4. Functions 28
13
If you don’t declare your function before you use it (either with a prototype or its definition), you’re per-
forming something called an implicit declaration. This was allowed in the first C standard (C89), and that
standard has rules about it, but is no longer allowed today. And there is no legitimate reason to rely on it in
new code.
You might notice something about the sample code we’ve been using… That is, we’ve been using the good old
printf() function without defining it or declaring a prototype! How do we get away with this lawlessness?
We don’t, actually. There is a prototype; it’s in that header file stdio.h that we included with #include,
remember? So we’re still legit, officer!
While the spec spells out that the behavior in this instance is as-if you’d indicated void (C11 §6.7.6.3¶14),
the void type is there for a reason. Use it.
But in the case of a function prototype, there is a significant difference between using void and not:
void foo();
void foo(void); // Not the same!
Leaving void out of the prototype indicates to the compiler that there is no additional information about the
parameters to the function. It effectively turns off all that type checking.
With a prototype definitely use void when you have an empty parameter list.
1
Never say “never”.
Chapter 5
Pointers—Cower In Fear!
1
Typically. I’m sure there are exceptions out there in the dark corridors of computing history.
2
A byte is a number made up of no more than 8 binary digits, or bits for short. This means in decimal digits just like grandma used
to use, it can hold an unsigned number between 0 and 255, inclusive.
29
Chapter 5. Pointers—Cower In Fear! 30
Memory Fun Facts: When you have a data type (like your typical int) that uses more than a
byte of memory, the bytes that make up the data are always adjacent to one another in memory.
Sometimes they’re in the order that you expect, and sometimes they’re not3 . While C doesn’t
guarantee any particular memory order (it’s platform-dependent), it’s still generally possible to
write code in a way that’s platform-independent where you don’t have to even consider these
pesky byte orderings.
So anyway, if we can get on with it and get a drum roll and some foreboding music playing for the definition
of a pointer, a pointer is a variable that holds an address. Imagine the classical score from 2001: A Space
Odyssey at this point. Ba bum ba bum ba bum BAAAAH!
Ok, so maybe a bit overwrought here, yes? There’s not a lot of mystery about pointers. They are the address
of data. Just like an int variable can hold the value 12, a pointer variable can hold the address of data.
This means that all these things mean the same thing, i.e. a number that represents a point in memory:
• Index into memory (if you’re thinking of memory like a big array)
• Address
• Location
I’m going to use these interchangeably. And yes, I just threw location in there because you can never have
enough words that mean the same thing.
And a pointer variable holds that address number. Just like a float variable might hold 3.14159.
Imagine you have a bunch of Post-it® notes all numbered in sequence with their address. (The first one is at
index numbered 0, the next at index 1, and so on.)
In addition to the number representing their positions, you can also write another number of your choice on
each. It could be the number of dogs you have. Or the number of moons around Mars…
…Or, it could be the index of another Post-it note!
If you have written the number of dogs you have, that’s just a regular variable. But if you wrote the index of
another Post-it in there, that’s a pointer. It points to the other note!
Another analogy might be with house addresses. You can have a house with certain qualities, yard, metal
roof, solar, etc. Or you could have the address of that house. The address isn’t the same as the house itself.
One’s a full-blown house, and the other is just a few lines of text. But the address of the house is a pointer
to that house. It’s not the house itself, but it tells you where to find it.
And we can do the same thing in the computer with data. You can have a data variable that’s holding some
value. And that value is in memory at some address. And you could have a different pointer variable hold
the address of that data variable.
It’s not the data variable itself, but, like with a house address, it tells us where to find it.
When we have that, we say we have a “pointer to” that data. And we can follow the pointer to access the
data itself.
(Though it doesn’t seem particularly useful yet, this all becomes indispensable when used with function calls.
Bear with me until we get there.)
So if we have an int, say, and we want a pointer to it, what we want is some way to get the address of that
int, right? After all, the pointer just holds the address of the data. What operator do you suppose we’d use
to find the address of the int?
Well, by a shocking surprise that must come as something of a shock to you, gentle reader, we use the
address-of operator (which happens to be an ampersand: “&”)to find the address of the data. Ampersand.
3
The order that bytes come in is referred to as the endianness of the number. The usual suspects are big-endian (with the most sig-
nificant byte first) and little-endian (with the most-significant byte last), or, uncommonly now, mixed-endian (with the most-significant
bytes somewhere else).
Chapter 5. Pointers—Cower In Fear! 31
So for a quick example, we’ll introduce a new format specifier for printf() so you can print a pointer. You
know already how %d prints a decimal integer, yes? Well, %p prints a pointer. Now, this pointer is going to
look like a garbage number (and it might be printed in hexadecimal4 instead of decimal), but it is merely the
index into memory the data is stored in. (Or the index into memory that the first byte of data is stored in,
if the data is multi-byte.) In virtually all circumstances, including this one, the actual value of the number
printed is unimportant to you, and I show it here only for demonstration of the address-of operator.
1 #include <stdio.h>
2
3 int main(void)
4 {
5 int i = 10;
6
If you’re curious, that hexadecimal number is 140,727,326,896,068 in decimal (base 10 just like Grandma
used to use). That’s the index into memory where the variable i’s data is stored. It’s the address of i. It’s
the location of i. It’s a pointer to i.
It’s a pointer because it lets you know where i is in memory. Like a home address written on a scrap of paper
tells you where you can find a particular house, this number indicates to us where in memory we can find
the value of i. It points to i.
Again, we don’t really care what the address’s exact number is, generally. We just care that it’s a pointer to
i.
Welcome back to another installment of Beej’s Guide. When we met last we were talking about how to make
use of pointers. Well, what we’re going to do is store a pointer off in a variable so that we can use it later.
You can identify the pointer type because there’s an asterisk (*) before the variable name and after its type:
1 int main(void)
2 {
3 int i; // i's type is "int"
4 int *p; // p's type is "pointer to an int", or "int-pointer"
5 }
4
That is, base 16 with digits 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, and F.
Chapter 5. Pointers—Cower In Fear! 32
Hey, so we have here a variable that is a pointer type, and it can point to other ints. That is, it can hold the
address of other ints. We know it points to ints, since it’s of type int* (read “int-pointer”).
When you do an assignment into a pointer variable, the type of the right hand side of the assignment has to
be the same type as the pointer variable. Fortunately for us, when you take the address-of a variable, the
resultant type is a pointer to that variable type, so assignments like the following are perfect:
int i;
int *p; // p is a pointer, but is uninitialized and points to garbage
On the left of the assignment, we have a variable of type pointer-to-int (int*), and on the right side, we
have expression of type pointer-to-int since i is an int (because address-of int gives you a pointer to int).
The address of a thing can be stored in a pointer to that thing.
Get it? I know it still doesn’t quite make much sense since you haven’t seen an actual use for the pointer
variable, but we’re taking small steps here so that no one gets lost. So now, let’s introduce you to the anti-
address-of operator. It’s kind of like what address-of would be like in Bizarro World.
5.3 Dereferencing
A pointer variable can be thought of as referring to another variable by pointing to it. It’s rare you’ll hear
anyone in C land talking about “referring” or “references”, but I bring it up just so that the name of this
operator will make a little more sense.
When you have a pointer to a variable (roughly “a reference to a variable”), you can use the original variable
through the pointer by dereferencing the pointer. (You can think of this as “de-pointering” the pointer, but
no one ever says “de-pointering”.)
Back to our analogy, this is vaguely like looking at a home address and then going to that house.
Now, what do I mean by “get access to the original variable”? Well, if you have a variable called i, and you
have a pointer to i called p, you can use the dereferenced pointer p exactly as if it were the original variable
i!
You almost have enough knowledge to handle an example. The last tidbit you need to know is actually
this: what is the dereference operator? It’s actually called the indirection operator, because you’re accessing
values indirectly via the pointer. And it is the asterisk, again: *. Now, don’t get this confused with the asterisk
you used in the pointer declaration, earlier. They are the same character, but they have different meanings in
different contexts5 .
Here’s a full-blown example:
1 #include <stdio.h>
2
3 int main(void)
4 {
5 int i;
6 int *p; // this is NOT a dereference--this is a type "int*"
7
10 i = 10; // i is now 10
11 *p = 20; // the thing p points to (namely i!) is now 20!!
12
5
That’s not all! It’s used in /*comments*/ and multiplication and in function prototypes with variable length arrays! It’s all the
same *, but the context gives it different meaning.
Chapter 5. Pointers—Cower In Fear! 33
Remember that p holds the address of i, as you can see where we did the assignment to p on line 8. What
the indirection operator does is tells the computer to use the object the pointer points to instead of using the
pointer itself. In this way, we have turned *p into an alias of sorts for i.
Great, but why? Why do any of this?
8 int main(void)
9 {
10 int i = 10;
11 int *j = &i; // note the address-of; turns it into a pointer to i
12
16 increment(j); // j is an int*--to i
Chapter 5. Pointers—Cower In Fear! 34
17
Ok! There are a couple things to see here… not the least of which is that the increment() function takes
an int* as an argument. We pass it an int* in the call by changing the int variable i to an int* using the
address-of operator. (Remember, a pointer holds an address, so we make pointers to variables by running
them through the address-of operator.)
The increment() function gets a copy of the pointer. Both the original pointer j (in main()) and the copy
of that pointer p (the parameter in increment()) point to the same address, namely the one holding the value
i. (Again, by analogy, like two pieces of paper with the same home address written on them.) Dereferencing
either will allow you to modify the original variable i! The function can modify a variable in another scope!
Rock on!
The above example is often more concisely written in the call just by using address-of right in the argument
list:
printf("i is %d\n", i); // prints "10"
increment(&i);
printf("i is %d\n", i); // prints "11"!
Pointer enthusiasts will recall from early on in the guide, we used a function to read from the keyboard,
scanf()… and, although you might not have recognized it at the time, we used the address-of to pass
a pointer to a value to scanf(). We had to pass a pointer, see, because scanf() reads from the keyboard
(typically) and stores the result in a variable. The only way it can see that variable out in the calling function’s
scope is if we pass a pointer to that variable:
int i = 0;
See, scanf() dereferences the pointer we pass it in order to modify the variable it points to. And now you
know why you have to put that pesky ampersand in there!
p = NULL;
Since it doesn’t point to a value, dereferencing it is undefined behavior, and probably will result in a crash:
int *p = NULL;
Despite being called the billion dollar mistake by its creator6 , the NULL pointer is a good sentinel value7 and
general indicator that a pointer hasn’t yet been initialized.
(Of course, like other variables, the pointer points to garbage unless you explicitly assign it to point to an
address or NULL.)
6
https://en.wikipedia.org/wiki/Null_pointer#History
7
https://en.wikipedia.org/wiki/Sentinel_value
Chapter 5. Pointers—Cower In Fear! 35
Can we make that into one line? We can. But where does the * go?
The rule is that the * goes in front of any variable that is a pointer type. That is. the * is not part of the int
in this example. it’s a part of variable p.
With that in mind, we can write this:
int a, *p; // Same thing
It’s important to note that the following line does not declare two pointers:
int *p, q; // p is a pointer to an int; q is just an int.
This can be particularly insidious-looking if the programmer writes this following (valid) line of code which
is functionally identical to the one above.
int* p, q; // p is a pointer to an int; q is just an int.
So take a look at this and determine which variables are pointers and which are not:
int *a, b, c, *d, e, *f, g, h, *i;
You might see code in the wild with that last sizeof in there. Just remember that sizeof is all about the
type of the expression, not the variables in the expression themselves.
8
The pointer type variables are a, d, f, and i, because those are the ones with * in front of them.
Chapter 6
Arrays
“Should array indices start at 0 or 1? My compromise of 0.5 was rejected without, I thought,
proper consideration.”
—Stan Kelly-Bootle, computer scientist
Luckily, C has arrays. I mean, I know it’s considered a low-level language1 but it does at least have the
concept of arrays built-in. And since a great many languages drew inspiration from C’s syntax, you’re
probably already familiar with using [ and ] for declaring and using arrays.
But C only barely has arrays! As we’ll find out later, arrays are just syntactic sugar in C—they’re actually
all pointers and stuff deep down. Freak out! But for now, let’s just use them as arrays. Phew.
3 int main(void)
4 {
5 int i;
6 float f[4]; // Declare an array of 4 floats
7
When you declare an array, you have to give it a size. And the size has to be fixed2 .
1
These days, anyway.
2
Again, not really, but variable-length arrays—of which I’m not really a fan—are a story for another time.
36
Chapter 6. Arrays 37
In the above example, we made an array of 4 floats. The value in the square brackets in the declaration lets
us know that.
Later on in subsequent lines, we access the values in the array, setting them or getting them, again with square
brackets.
Hopefully this looks familiar from languages you already know!
If it’s an array of chars, then sizeof the array is the number of elements, since sizeof(char) is defined
to be 1. For anything else, you have to divide by the size of each element.
But this trick only works in the scope in which the array was defined. If you pass the array to a function, it
doesn’t work. Even if you make it “big” in the function signature:
void foo(int x[12])
{
printf("%zu\n", sizeof x); // 8?! What happened to 48?
printf("%zu\n", sizeof(int)); // 4 bytes per int
This is because when you “pass” arrays to functions, you’re only passing a pointer to the first element, and
that’s what sizeof measures. More on this in the Passing Single Dimensional Arrays to Functions section,
below.
One more thing you can do with sizeof and arrays is get the size of an array of a fixed number of elements
without declaring the array. This is like how you can get the size of an int with sizeof(int).
For example, to see how many bytes would be needed for an array of 48 doubles, you can do this:
sizeof(double [48]);
1 #include <stdio.h>
2
3 int main(void)
4 {
5 int i;
6 int a[5] = {22, 37, 3490, 18, 95}; // Initialize with these values
7
Catch: initializer values must be constant terms. Can’t throw variables in there. Sorry, Illinois!
You should never have more items in your initializer than there is room for in the array, or the compiler will
get cranky:
foo.c: In function ‘main’:
foo.c:6:39: warning: excess elements in array initializer
6 | int a[5] = {22, 37, 3490, 18, 95, 999};
| ^~~
foo.c:6:39: note: (near initialization for ‘a’)
But (fun fact!) you can have fewer items in your initializer than there is room for in the array. The remaining
elements in the array will be automatically initialized with zero. This is true in general for all types of array
initializers: if you have an initializer, anything not explicitly set to a value will be set to zero.
int a[5] = {22, 37, 3490};
It’s a common shortcut to see this in an initializer when you want to set an entire array to zero:
int a[100] = {0};
Which means, “Make the first element zero, and then automatically make the rest zero, as well.”
You can set specific array elements in the initializer, as well, by specifying an index for the value! When
you do this, C will happily keep initializing subsequent values for you until the initializer runs out, filling
everything else with 0.
To do this, put the index in square brackets with an = after, and then set the value.
Here’s an example where we build an array:
int a[10] = {0, 11, 22, [5]=55, 66, 77};
Because we listed index 5 as the start for 55, the resulting data in the array is:
0 11 22 0 0 55 66 77 0 0
0 0 3 2 1
Lastly, you can also have C compute the size of the array from the initializer, just by leaving the size off:
int a[3] = {22, 37, 3490};
3 int main(void)
4 {
5 int i;
6 int a[5] = {22, 37, 3490, 18, 95};
7
8 for (i = 0; i < 10; i++) { // BAD NEWS: printing too many elements!
9 printf("%d\n", a[i]);
10 }
11 }
Yikes! What’s that? Well, turns out printing off the end of an array results in what C developers call undefined
behavior. We’ll talk more about this beast later, but for now it means, “You’ve done something bad, and
anything could happen during your program run.”
And by anything, I mean typically things like finding zeroes, finding garbage numbers, or crashing. But
really the C spec says in this circumstance the compiler is allowed to emit code that does anything5 .
Short version: don’t do anything that causes undefined behavior. Ever6 .
5
In the good old MS-DOS days before memory protection was a thing, I was writing some particularly abusive C code that deliber-
ately engaged in all kinds of undefined behavior. But I knew what I was doing, and things were working pretty well. Until I made a
misstep that caused a lockup and, as I found upon reboot, nuked all my BIOS settings. That was fun. (Shout-out to @man for those fun
times.)
6
There are a lot of things that cause undefined behavior, not just out-of-bounds array accesses. This is what makes the C language
so exciting.
Chapter 6. Arrays 40
These are stored in memory in row-major order7 . This means with a 2D array, the first index listed indicates
the row, and the second the column.
You can also use initializers on multidimensional arrays by nesting them:
1 #include <stdio.h>
2
3 int main(void)
4 {
5 int row, col;
6
3 int main(void)
4 {
5 int a[5] = {11, 22, 33, 44, 55};
6 int *p;
7
Just referring to the array name in isolation is the same as getting a pointer to the first element of the array!
We’re going to use this extensively in the upcoming examples.
But hold on a second—isn’t p an int*? And *p gives us 11, same as a[0]? Yessss. You’re starting to get a
glimpse of how arrays and pointers are related in C.
8
This is technically incorrect, as a pointer to an array and a pointer to the first element of an array have different types. But we can
burn that bridge when we get to it.
Chapter 6. Arrays 42
24 int main(void)
25 {
26 int x[5] = {11, 22, 33, 44, 55};
27
28 times2(x, 5);
29 times3(x, 5);
30 times4(x, 5);
31 }
All those methods of listing the array as a parameter in the function are identical.
void times2(int *a, int len)
void times3(int a[], int len)
void times4(int a[5], int len)
9
C11 §6.7.6.2¶1 requires it be greater than zero. But you might see code out there with arrays declared of zero length at the end of
structs and GCC is particularly lenient about it unless you compile with -pedantic. This zero-length array was a hackish mechanism
for making variable-length structures. Unfortunately, it’s technically undefined behavior to access such an array even though it basically
worked everywhere. C99 codified a well-defined replacement for it called flexible array members, which we’ll chat about later.
Chapter 6. Arrays 43
14 int main(void)
15 {
16 int x[5] = {1, 2, 3, 4, 5};
17
18 double_array(x, 5);
19
Even though we passed the array in as parameter a which is type int*, look at how we access it using array
notation with a[i]! Whaaaat. This is totally allowed.
Later when we talk about the equivalence between arrays and pointers, we’ll see how this makes a lot more
sense. For now, it’s enough to know that functions can make changes to arrays that are visible out in the
caller.
12 int main(void)
13 {
14 int x[2][3] = {
15 {1, 2, 3},
16 {4, 5, 6}
17 };
18
19 print_2D_array(x);
20 }
Chapter 6. Arrays 44
The compiler really only needs the second dimension so it can figure out how far in memory to skip for each
increment of the first dimension. In general, it needs to know all the dimensions except the first one.
Also, remember that the compiler does minimal compile-time bounds checking (if you’re lucky), and C does
zero runtime checking of bounds. No seat belts! Don’t crash by accessing array elements out of bounds!
10
This is also equivalent: void print_2D_array(int (*a)[3]), but that’s more than I want to get into right now.
Chapter 7
Strings
The first one has a newline at the end—quite a common thing to see.
The last one has quotes embedded within it, but you see each is preceded by (we say “escaped by”) a backslash
(\) indicating that a literal quote belongs in the string at this point. This is how the C compiler can tell the
difference between printing a double quote and the double quote at the end of the string.
Check out that type: pointer to a char. The string variable s is actually a pointer to the first character in that
string, namely the H.
And we can print it with the %s (for “string”) format specifier:
char *s = "Hello, world!";
45
Chapter 7. Strings 46
This means you can use array notation to access characters in a string. Let’s do exactly that to print all the
characters in a string on the same line:
1 #include <stdio.h>
2
3 int main(void)
4 {
5 char s[] = "Hello, world!";
6
Note that we’re using the format specifier %c to print a single character.
Also, check this out. The program will still work fine if we change the definition of s to be a char* type:
1 #include <stdio.h>
2
3 int main(void)
4 {
5 char *s = "Hello, world!"; // char* here
6
And we still can use array notation to get the job done when printing it out! This is surprising, but is still
only because we haven’t talked about array/pointer equivalence yet. But this is yet another hint that arrays
and pointers are the same thing, deep down.
The behavior is undefined. Probably, depending on your system, a crash will result.
But declaring it as an array is different. This one is a mutable copy of the string that we can change at will:
char t[] = "Hello, again!"; // t is an array copy of the string
t[0] = 'z'; // No problem
So remember: if you have a pointer to a string literal, don’t try to change it! And if you use a string in double
quotes to initialize an array, that’s not actually a string literal.
4 int main(void)
5 {
6 char *s = "Hello, world!";
7
The strlen() function returns type size_t, which is an integer type so you can use it for integer math. We
print size_t with %zu.
The above program prints:
The string is 13 bytes long.
Of course, these days it seems ridiculous to worry about saving a byte (or 3—lots of languages will happily
let you have strings that are 4 gigabytes in length). But back in the day, it was a bigger deal.
So C took approach #2. In C, a “string” is defined by two basic characteristics:
• A pointer to the first character in the string.
• A zero-valued byte (or NUL character3 ) somewhere in memory after the pointer that indicates the end
of the string.
A NUL character can be written in C code as \0, though you don’t often have to do this.
When you include a string in double quotes in your code, the NUL character is automatically, implicitly
included.
char *s = "Hello!"; // Actually "Hello!\0" behind the scenes
So with this in mind, let’s write our own strlen() function that counts chars in a string until it finds a NUL.
The procedure is to look down the string for a single NUL character, counting as we go4 :
int my_strlen(char *s)
{
int count = 0;
return count;
}
And that’s basically how the built-in strlen() gets the job done.
3 int main(void)
4 {
5 char s[] = "Hello, world!";
6 char *t;
7
11 // We modify t
12 t[0] = 'z';
13
3
This is different than the NULL pointer, and I’ll abbreviate it NUL when talking about the character versus NULL for the pointer.
4
Later we’ll learn a neater way to do it with pointer arithmetic.
Chapter 7. Strings 49
If you want to make a copy of a string, you have to copy it a byte at a time—but this is made easier with the
strcpy() function5 .
Before you copy the string, make sure you have room to copy it into, i.e. the destination array that’s going
to hold the characters needs to be at least as long as the string you’re copying.
1 #include <stdio.h>
2 #include <string.h>
3
4 int main(void)
5 {
6 char s[] = "Hello, world!";
7 char t[100]; // Each char is one byte, so plenty of room
8
12 // We modify t
13 t[0] = 'z';
14
Notice with strcpy(), the destination pointer is the first argument, and the source pointer is the second. A
mnemonic I use to remember this is that it’s the order you would have put t and s if an assignment = worked
for strings, with the source on the right and the destination on the left.
5
There’s a safer function called strncpy() that you should probably use instead, but we’ll get to that later.
Chapter 8
Structs
In C, we have something called a struct, which is a user-definable type that holds multiple pieces of data,
potentially of different types.
It’s a convenient way to bundle multiple variables into a single one. This can be beneficial for passing
variables to functions (so you just have to pass one instead of many), and useful for organizing data and
making code more readable.
If you’ve come from another language, you might be familiar with the idea of classes and objects. These
don’t exist in C, natively1 . You can think of a struct as a class with only data members, and no methods.
This is often done at the global scope outside any functions so that the struct is globally available.
When you do this, you’re making a new type. The full type name is struct car. (Not just car—that won’t
work.)
There aren’t any variables of that type yet, but we can declare some:
struct car saturn; // Variable "saturn" of type "struct car"
1
Although in C individual items in memory like ints are referred to as “objects”, they’re not objects in an object-oriented program-
ming sense.
2
The Saturn was a popular brand of economy car in the United States until it was put out of business by the 2008 crash, sadly so to
us fans.
50
Chapter 8. Structs 51
saturn.speed = 175;
There on the first lines, we set the values in the struct car, and then in the next bit, we print those values
out.
You can do it with an initializer by putting values in for the fields in the order they appear in the struct
when you define the variable. (This won’t work after the variable has been defined—it has to happen in the
definition).
struct car {
char *name;
float price;
int speed;
};
The fact that the fields in the initializer need to be in the same order is a little freaky. If someone changes the
order in struct car, it could break all the other code!
We can be more specific with our initializers:
struct car saturn = {.speed=175, .name="Saturn SL/2"};
Now it’s independent of the order in the struct declaration. Which is safer code, for sure.
Similar to array initializers, any missing field designators are initialized to zero (in this case, that would be
.price, which I’ve omitted).
2. The struct is somewhat large and it’s more expensive to copy that onto the stack than it is to just
copy a pointer3 .
For those two reasons, it’s far more common to pass a pointer to a struct to a function, though its by no
means illegal to pass the struct itself.
Let’s try passing in a pointer, making a function that will allow you to set the .price field of the struct
car:
1 #include <stdio.h>
2
3 struct car {
4 char *name;
5 float price;
6 int speed;
7 };
8
9 int main(void)
10 {
11 struct car saturn = {.speed=175, .name="Saturn SL/2"};
12
You should be able to come up with the function signature for set_price() just by looking at the types of
the arguments we have there.
saturn is a struct car, so &saturn must be the address of the struct car, AKA a pointer to a struct
car, namely a struct car*.
That won’t work because the dot operator only works on structs… it doesn’t work on pointers to structs.
Ok, so we can dereference the struct to de-pointer it to get to the struct itself. Dereferencing a struct
car* results in the struct car that the pointer points to, which we should be able to use the dot operator
on:
void set_price(struct car *c, float new_price) {
(*c).price = new_price; // Works, but is ugly and non-idiomatic :(
}
And that works! But it’s a little clunky to type all those parens and the asterisk. C has some syntactic sugar
called the arrow operator that helps with that.
3
A pointer is likely 8 bytes on a 64-bit system.
Chapter 8. Structs 53
So when accessing fields, when do we use dot and when do we use arrow?
• If you have a struct, use dot (.).
• If you have a pointer to a struct, use arrow (->).
And returning a struct (as opposed to a pointer to one) from a function also makes a similar copy to the
receiving variable.
This is not a “deep copy”4 . All fields are copied as-is, including pointers to things.
4
A deep copy follows pointer in the struct and copies the data they point to, as well. A shallow copy just copies the pointers, but
not the things they point to. C doesn’t come with any built-in deep copy functionality.
5
https://beej.us/guide/bgclr/html/split/stringref.html#man-strcmp
6
https://beej.us/guide/bgclr/html/split/stringref.html#man-memset
7
https://stackoverflow.com/questions/141720/how-do-you-compare-structs-for-equality-in-c
Chapter 9
File Input/Output
We’ve already seen a couple examples of I/O with scanf() and printf() for doing I/O at the console
(screen/keyboard).
But we’ll push those concepts a little farther this chapter.
We’ve actually been using these implicitly already, it turns out. For example, these two calls are the same:
printf("Hello, world!\n");
fprintf(stdout, "Hello, world!\n"); // printf to a file
54
Chapter 9. File Input/Output 55
For this reason, you should send serious error messages to stderr instead of stdout.
More on how to do that later.
And let’s write a program to open the file, read a character out of it, and then close the file when we’re done.
That’s the game plan!
1 #include <stdio.h>
2
3 int main(void)
4 {
5 FILE *fp; // Variable to represent open file
6
See how when we opened the file with fopen(), it returned the FILE* to us so we could use it later.
(I’m leaving it out for brevity, but fopen() will return NULL if something goes wrong, like file-not-found,
so you should really error check it!)
Also notice the "r" that we passed in—this means “open a text stream for reading”. (There are various
strings we can pass to fopen() with additional meaning, like writing, or appending, and so on.)
After that, we used the fgetc() function to get a character from the stream. You might be wondering why
I’ve made c an int instead of a char—hold that thought!
Finally, we close the stream when we’re done with it. All streams are automatically closed when the program
exits, but it’s good form and good housekeeping to explicitly close any files yourself when done with them.
The FILE* keeps track of our position in the file. So subsequent calls to fgetc() would get the next character
in the file, and then the next, until the end.
1
We used to have three different newlines in broad effect: Carriage Return (CR, used on old Macs), Linefeed (LF, used on Unix
systems), and Carriage Return/Linefeed (CRLF, used on Windows systems). Thankfully the introduction of OS X, being Unix-based,
reduced this number to two.
Chapter 9. File Input/Output 56
But that sounds like a pain. Let’s see if we can make it easier.
3 int main(void)
4 {
5 FILE *fp;
6 int c;
7
8 fp = fopen("hello.txt", "r");
9
13 fclose(fp);
14 }
(If line 10 is too weird, just break it down starting with the innermost-nested parens. The first thing we do
is assign the result of fgetc() into c, and then we compare that against EOF. We’ve just crammed it into a
single line. This might look hard to read, but study it—it’s idiomatic C.)
And running this, we see:
Hello, world!
But still, we’re operating a character at a time, and lots of text files make more sense at the line level. Let’s
switch to that.
And here’s some code that reads that file a line at a time and prints out a line number before each one:
1 #include <stdio.h>
2
3 int main(void)
4 {
5 FILE *fp;
6 char s[1024]; // Big enough for any line this program will encounter
7 int linecount = 0;
8
9 fp = fopen("quote.txt", "r");
10
14 fclose(fp);
15 }
Yes, we could read these with fgets() and then parse the string with sscanf() (and in some ways that’s
more resilient against corrupted files), but in this case, let’s just use fscanf() and pull it in directly.
The fscanf() function skips leading whitespace when reading, and returns EOF on end-of-file or error.
1 #include <stdio.h>
2
3 int main(void)
4 {
5 FILE *fp;
6 char name[1024]; // Big enough for any line this program will encounter
7 float length;
8 int mass;
9
10 fp = fopen("whales.txt", "r");
11
14
15 fclose(fp);
16 }
To do so, we have to fopen() the file in write mode by passing "w" as the second argument. Opening an
existing file in "w" mode will instantly truncate that file to 0 bytes for a full overwrite.
We’ll put together a simple program that outputs a file output.txt using a variety of output functions.
1 #include <stdio.h>
2
3 int main(void)
4 {
5 FILE *fp;
6 int x = 32;
7
8 fp = fopen("output.txt", "w");
9
10 fputc('B', fp);
11 fputc('\n', fp); // newline
12 fprintf(fp, "x = %d\n", x);
13 fputs("Hello, world!\n", fp);
14
15 fclose(fp);
16 }
Fun fact: since stdout is a file, you could replace line 8 with:
fp = stdout;
and the program would have outputted to the console instead of to a file. Try it!
The big difference in opening the file is that you have to add a "b" to the mode. That is, to read a binary file,
open it in "rb" mode. To write a file, open it in "wb" mode.
Because it’s streams of bytes, and streams of bytes can contain NUL characters, and the NUL character is
the end-of-string marker in C, it’s rare that people use the fprintf()-and-friends functions to operate on
binary files.
Instead the most common functions are fread() and fwrite(). The functions read and write a specified
number of bytes to the stream.
To demo, we’ll write a couple programs. One will write a sequence of byte values to disk all at once. And
the second program will read a byte at a time and print them out3 .
1 #include <stdio.h>
2
3 int main(void)
4 {
5 FILE *fp;
6 unsigned char bytes[6] = {5, 37, 0, 88, 255, 12};
7
19 fclose(fp);
20 }
Those two middle arguments to fwrite() are pretty odd. But basically what we want to tell the function is,
“We have items that are this big, and we want to write that many of them.” This makes it convenient if you
have a record of a fixed length, and you have a bunch of them in an array. You can just tell it the size of one
record and how many to write.
In the example above, we tell it each record is the size of a char, and we have 6 of them.
Running the program gives us a file output.bin, but opening it in a text editor doesn’t show anything
friendly! It’s binary data—not text. And random binary data I just made up, at that!
If I run it through a hex dump4 program, we can see the output as bytes:
05 25 00 58 ff 0c
And those values in hex do match up to the values (in decimal) that we wrote out.
But now let’s try to read them back in with a different program. This one will open the file for binary reading
("rb" mode) and will read the bytes one at a time in a loop.
fread() has the neat feature where it returns the number of bytes read, or 0 on EOF. So we can loop until
we see that, printing numbers as we go.
3
Normally the second program would read all the bytes at once, and then print them out in a loop. That would be more efficient.
But we’re going for demo value, here.
4
https://en.wikipedia.org/wiki/Hex_dump
Chapter 9. File Input/Output 60
1 #include <stdio.h>
2
3 int main(void)
4 {
5 FILE *fp;
6 unsigned char c;
7
Woo hoo!
This means that if you write a multibyte number out straight from memory, you can’t do it in a portable way6 .
A similar problem exists with floating point. Most systems use the same format for their floating point
numbers, but some do not. No guarantees!
So… how can we fix all these problems with numbers and structs to get our data written in a portable way?
The summary is to serialize the data, which is a general term that means to take all the data and write it out
in a format that you control, that is well-known, and programmable to work the same way on all platforms.
As you might imagine, this is a solved problem. There are a bunch of serialization libraries you can take
advantage of, such as Google’s protocol buffers7 , out there and ready to use. They will take care of all the
gritty details for you, and even will allow data from your C programs to interoperate with other languages
that support the same serialization methods.
Do yourself and everyone a favor! Serialize your binary data when you write it to a stream! This will keep
things nice and portable, even if you transfer data files from one architecture to another.
6
And this is why I used individual bytes in my fwrite() and fread() examples, above, shrewdly.
7
https://en.wikipedia.org/wiki/Protocol_buffers
Chapter 10
Well, not so much making new types as getting new names for existing types. Sounds kinda pointless on the
surface, but we can really use this to make our code cleaner.
You can take any existing type and do it. You can even make a number of types with a comma list:
typedef int antelope, bagel, mushroom; // These are all "int"
That’s really useful, right? That you can type mushroom instead of int? You must be super excited about
this feature!
OK, Professor Sarcasm—we’ll get to some more common applications of this in a moment.
10.1.1 Scoping
typedef follows regular scoping rules.
For this reason, it’s quite common to find typedef at file scope (“global”) so that all functions can use the
new types at will.
62
Chapter 10. typedef: Making New Types 63
struct animal {
char *name;
int leg_count, speed;
};
Personally, I don’t care for this practice. I like the clarity the code has when you add the word struct to the
type; programmers know what they’re getting. But it’s really common so I’m including it here.
Now I want to run the exact same example in a way that you might commonly see. We’re going to put the
struct animal in the typedef. You can mash it all together like this:
// original name
// |
// v
// |-----------|
typedef struct animal {
char *name;
int leg_count, speed;
} animal; // <-- new name
That’s exactly the same as the previous example, just more concise.
But that’s not all! There’s another common shortcut that you might see in code using what are called anony-
mous structures1 . It turns out you don’t actually need to name the structure in a variety of places, and with
typedef is one of them.
1
We’ll talk more about these later.
Chapter 10. typedef: Making New Types 64
} point;
// and
Then if later you want to change to another type, like long double, you just need to change the typedef:
// voila!
// |---------|
typedef long double app_float;
app_float f1, f2, f3; // Now these are all long doubles
int a = 10;
intptr x = &a; // "intptr" is type "int*"
I really don’t like this practice. It hides the fact that x is a pointer type because you don’t see a * in the
declaration.
IMHO, it’s better to explicitly show that you’re declaring a pointer type so that other devs can clearly see it
and don’t mistake x for having a non-pointer type.
But at last count, say, 832,007 people had a different opinion.
typedef struct {
Chapter 10. typedef: Making New Types 65
int x, y;
} MyPoint; // CamelCase
typedef struct {
int x, y;
} Mypoint; // Leading uppercase
typedef struct {
int x, y;
} MY_POINT; // UPPER SNAKE CASE
The C11 specification doesn’t dictate one way or another, and shows examples in all uppercase and all low-
ercase.
K&R2 uses leading uppercase predominantly, but show some examples in uppercase and snake case (with
_t).
If you have a style guide in use, stick with it. If you don’t, grab one and stick with it.
I don’t like it because it hides the array nature of the variable, but it’s possible to do.
Chapter 11
Time to get more into it with a number of new pointer topics! If you’re not up to speed with pointers, check
out the first section in the guide on the matter.
Now let’s use pointer arithmetic to print the next element in the array, the one at index 1:
printf("%d\n", *(p + 1)); // Prints 22!!
What happened there? C knows that p is a pointer to an int. So it knows the sizeof an int1 and it knows
to skip that many bytes to get to the next int after the first one!
1
Recall that the sizeof operator tells you the size in bytes of an object in memory.
66
Chapter 11. Pointers II: Arithmetic 67
In fact, the prior example could be written these two equivalent ways:
printf("%d\n", *p); // Prints 11
printf("%d\n", *(p + 0)); // Prints 11
And that works the same as if we used array notation! Oooo! Getting closer to that array/pointer equivalence
thing! More on this later in this chapter.
But what’s actually happening, here? How does it work?
Remember from early on that memory is like a big array, where a byte is stored at each array index?
And the array index into memory has a few names:
• Index into memory
• Location
• Address
• Pointer!
So a point is an index into memory, somewhere.
For a random example, say that a number 3490 was stored at address (“index”) 23,237,489,202. If we have
an int pointer to that 3490, that value of that pointer is 23,237,489,202… because the pointer is the memory
address. Different words for the same thing.
And now let’s say we have another number, 4096, stored right after the 3490 at address 23,237,489,210 (8
higher than the 3490 because each int in this example is 8 bytes long).
If we add 1 to that pointer, it actually jumps ahead sizeof(int) bytes to the next int. It knows to jump
that far ahead because it’s an int pointer. If it were a float pointer, it’d jump sizeof(float) bytes ahead
to get to the next float!
So you can look at the next int, by adding 1 to the pointer, the one after that by adding 2 to the pointer, and
so on.
And we also have p pointing to the element at index 0 of a, namely 11, just like before.
Chapter 11. Pointers II: Arithmetic 68
Now—let’s start incrementing p so that it points at subsequent elements of the array. We’ll do this until p
points to the 999; that is, we’ll do it until *p == 999:
while (*p != 999) { // While the thing p points to isn't 999
printf("%d\n", *p); // Print it
p++; // Move p to point to the next int!
}
16 int main(void)
17 {
18 printf("%d\n", my_strlen("Hello, world!")); // Prints "13"
19 }
Remember that you can only use pointer subtraction between two pointers that point to the same array!
2
Or string, which is really an array of chars. Somewhat peculiarly, you can also have a pointer that references one past the end of
the array without a problem and still do math on it. You just can’t dereference it when it’s out there.
Chapter 11. Pointers II: Arithmetic 69
but that’s a little harder to grok. Just make sure you include parentheses if the expressions are complicated
so all your math happens in the right order.
This means we can decide if we’re going to use array or pointer notation for any array or pointer (assuming
it points to an element of an array).
Let’s use an array and pointer with both array and pointer notation:
1 #include <stdio.h>
2
3 int main(void)
4 {
5 int a[] = {11, 22, 33, 44, 55};
6
So you can see that in general, if you have an array variable, you can use pointer or array notion to access
elements. Same with a pointer variable.
The one big difference is that you can modify a pointer to point to a different address, but you can’t do that
with an array variable.
Chapter 11. Pointers II: Arithmetic 70
this means you can pass either an array or a pointer to this function and have it work!
char s[] = "Antelopes";
char *t = "Wombats";
And it’s also why these two function signatures are equivalent:
int my_strlen(char *s) // Works!
int my_strlen(char s[]) // Works, too!
This function copies n bytes of memory starting from address s1 into the memory starting at address s2.
But look! s1 and s2 are void*s! Why? What does it mean? Let’s run more examples to see.
For instance, we could copy a string with memcpy() (though strcpy() is more appropriate for strings):
1 #include <stdio.h>
2 #include <string.h>
3
4 int main(void)
5 {
6 char s[] = "Goats!";
7 char t[100];
8
3
https://beej.us/guide/bgclr/html/split/stdlib.html#man-qsort
4
https://beej.us/guide/bgclr/html/split/stdlib.html#man-bsearch
Chapter 11. Pointers II: Arithmetic 71
4 int main(void)
5 {
6 int a[] = {11, 22, 33};
7 int b[3];
8
11 printf("%d\n", b[1]); // 22
12 }
That one’s a little wild—you see what we did there with memcpy()? We copied the data from a to b, but we
had to specify how many bytes to copy, and an int is more than one byte.
OK, then—how many bytes does an int take? Answer: depends on the system. But we can tell how many
bytes any type takes with the sizeof operator.
So there’s the answer: an int takes sizeof(int) bytes of memory to store.
And if we have 3 of them in our array, like we did in that example, the entire space used for the 3 ints must
be 3 * sizeof(int).
(In the string example, earlier, it would have been more technically accurate to copy 7 * sizeof(char)
bytes. But chars are always one byte large, by definition, so that just devolves into 7 * 1.)
We could even copy a float or a struct with memcpy()! (Though this is abusive—we should just use =
for that):
struct antelope my_antelope;
struct antelopy my_clone_antelope;
// ...
Look at how versatile memcpy() is! If you have a pointer to a source and a pointer to a destination, and you
have the number of bytes you want to copy, you can copy any type of data.
Imagine if we didn’t have void*. We’d have to write specialized memcpy() functions for each type:
memcpy_int(int *a, int *b, int count);
memcpy_float(float *a, float *b, int count);
memcpy_double(double *a, double *b, int count);
memcpy_char(char *a, char *b, int count);
memcpy_unsigned_char(unsigned char *a, unsigned char *b, int count);
// etc... blech!
Much better to just use void* and have one function that can do it all.
Chapter 11. Pointers II: Arithmetic 72
That’s the power of void*. You can write functions that don’t care about the type and is still able to do things
with it.
But with great power comes great responsibility. Maybe not that great in this case, but there are some limits.
1. You cannot do pointer arithmetic on a void*. 2. You cannot dereference a void*. 3. You cannot use the
arrow operator on a void*, since it’s also a dereference. 4. You cannot use array notation on a void*, since
it’s also a dereference, as well5 .
And if you think about it, these rules make sense. All those operations rely on knowing the sizeof the type
of data pointed to, and with void*, we don’t know the size of the data being pointed to—it could be anything!
But wait—if you can’t dereference a void* what good can it ever do you?
Like with memcpy(), it helps you write generic functions that can handle multiple types of data. But the
secret is that, deep down, you convert the void* to another type before you use it!
And conversion is easy: you can just assign into a variable of the desired type6 .
char a = 'X'; // A single char
Let’s write our own memcpy() to try this out. We can copy bytes (chars), and we know the number of bytes
because it’s passed in.
void *my_memcpy(void *dest, void *src, int byte_count)
{
// Convert void*s to char*s
char *s = src, *d = dest;
Right there at the beginning, we copy the void*s into char*s so that we can use them as char*s. It’s as
easy as that.
Then some fun in a while loop, where we decrement byte_count until it becomes false (0). Remember
that with post-decrement, the value of the expression is computed (for while to use) and then the variable is
decremented.
And some fun in the copy, where we assign *d = *s to copy the byte, but we do it with post-increment so
that both d and s move to the next byte after the assignment is made.
Lastly, most memory and string functions return a copy of a pointer to the destination string just in case the
caller wants to use it.
5
Because remember that array notation is just a dereference and some pointer math, and you can’t dereference a void*!
6
You can also cast the void* to another type, but we haven’t gotten to casts yet.
Chapter 11. Pointers II: Arithmetic 73
Now that we’ve done that, I just want to quickly point out that we can use this technique to iterate over the
bytes of any object in C, floats, structs, or anything!
Let’s run one more real-world example with the built-in qsort() routine that can sort anything thanks to the
magic of void*s.
(In the following example, you can ignore the word const, which we haven’t covered yet.)
1 #include <stdio.h>
2 #include <stdlib.h>
3
30 return 0;
31 }
32
33 int main(void)
34 {
35 // Let's build an array of 4 struct animals with different
36 // characteristics. This array is out of order by leg_count, but
37 // we'll sort it in a second.
38 struct animal a[4] = {
39 {.name="Dog", .leg_count=4},
40 {.name="Monkey", .leg_count=2},
41 {.name="Antelope", .leg_count=4},
42 {.name="Snake", .leg_count=0}
43 };
44
48 //
49 // This call is saying: qsort array a, which has 4 elements, and
50 // each element is sizeof(struct animal) bytes big, and this is the
51 // function that will compare any two elements.
52 qsort(a, 4, sizeof(struct animal), compar);
53
As long as you give qsort() a function that can compare two items that you have in your array to be sorted, it
can sort anything. And it does this without needing to have the types of the items hardcoded in there anywhere.
qsort() just rearranges blocks of bytes based on the results of the compar() function you passed in.
Chapter 12
This is one of the big areas where C likely diverges from languages you already know: manual memory
management.
Other languages uses reference counting, garbage collection, or other means to determine when to allocate
new memory for some data—and when to deallocate it when no variables refer to it.
And that’s nice. It’s nice to be able to not worry about it, to just drop all the references to an item and trust
that at some point the memory associated with it will be freed.
But C’s not like that, entirely.
Of course, in C, some variables are automatically allocated and deallocated when they come into scope and
leave scope. We call these automatic variables. They’re your average run-of-the-mill block scope “local”
variables. No problem.
But what if you want something to persist longer than a particular block? This is where manual memory
management comes into play.
You can tell C explicitly to allocate for you a certain number of bytes that you can use as you please. And
these bytes will remain allocated until you explicitly free that memory1 .
It’s important to free the memory you’re done with! If you don’t, we call that a memory leak and your process
will continue to reserve that memory until it exits.
If you manually allocated it, you have to manually free it when you’re done with it.
So how do we do this? We’re going to learn a couple new functions, and make use of the sizeof operator
to help us learn how many bytes to allocate.
In common C parlance, devs say that automatic local variables are allocated “on the stack”, and manually-
allocated memory is “on the heap”. The spec doesn’t talk about either of those things, but all C devs will
know what you’re talking about if you bring them up.
All functions we’re going to learn in this chapter can be found in <stdlib.h>.
75
Chapter 12. Manual Memory Allocation 76
Since it’s a void*, you can assign it into whatever pointer type you want… normally this will correspond in
some way to the number of bytes you’re allocating.
So… how many bytes should I allocate? We can use sizeof to help with that. If we want to allocate enough
room for a single int, we can use sizeof(int) and pass that to malloc().
After we’re done with some allocated memory, we can call free() to indicate we’re done with that memory
and it can be used for something else. As an argument, you pass the same pointer you got from malloc()
(or a copy of it). It’s undefined behavior to use a memory region after you free() it.
Let’s try. We’ll allocate enough memory for an int, and then store something there, and the print it.
// Allocate space for a single int (sizeof(int) bytes-worth):
int *p = malloc(sizeof(int));
Now, in that contrived example, there’s really no benefit to it. We could have just used an automatic int
and it would have worked. But we’ll see how the ability to allocate memory this way has its advantages,
especially with more complex data structures.
One more thing you’ll commonly see takes advantage of the fact that sizeof can give you the size of the
result type of any constant expression. So you could put a variable name in there, too, and use that. Here’s
an example of that, just like the previous one:
int *p = malloc(sizeof *p); // *p is an int, so same as sizeof(int)
x = malloc(sizeof(int) * 10);
if (x == NULL) {
printf("Error allocating 10 ints\n");
// do something here to handle it
}
Here’s a common pattern that you’ll see, where we do the assignment and the condition on the same line:
int *x;
And—indeed!—that’s an array of 3490 chars (AKA a string!) since each char is 1 byte. In other words,
sizeof(char) is 1.
Note: there’s no initialization done on the newly-allocated memory—it’s full of garbage. Clear it with mem-
set() if you want to, or see calloc(), below.
But we can just multiply the size of the thing we want by the number of elements we want, and then access
them using either pointer or array notation. Example!
1 #include <stdio.h>
2 #include <stdlib.h>
3
4 int main(void)
5 {
6 // Allocate space for 10 ints
7 int *p = malloc(sizeof(int) * 10);
8
The key’s in that malloc() line. If we know each int takes sizeof(int) bytes to hold it, and we know
we want 10 of them, we can just allocate exactly that many bytes with:
sizeof(int) * 10
And this trick works for every type. Just pass it to sizeof and multiply by the size of the array.
Again, the result is the same for both except malloc() doesn’t zero the memory by default.
Let’s allocate an array of 20 floats, and then change our mind and make it an array of 40.
We’re going to assign the return value of realloc() into another pointer just to make sure it’s not NULL. If
it’s not, then we can reassign it into our original pointer. (If we just assigned the return value directly into the
original pointer, we’d lose that pointer if the function returned NULL and we’d have no way to get it back.)
1 #include <stdio.h>
2 #include <stdlib.h>
3
4 int main(void)
5 {
6 // Allocate space for 20 floats
7 float *p = malloc(sizeof *p * 20); // sizeof *p same as sizeof(float)
8
Notice in there how we took the return value from realloc() and reassigned it into the same pointer variable
p that we passed in. That’s pretty common to do.
Also if line 7 is looking weird, with that sizeof *p in there, remember that sizeof works on the size of
the type of the expression. And the type of *p is float, so that line is equivalent to sizeof(float).
36 if (new_buf == NULL) {
37 free(buf); // On error, free and bail
38 return NULL;
39 }
40
56 // Shrink to fit
57 if (offset < bufsize - 1) { // If we're short of the end
58 char *new_buf = realloc(buf, offset + 1); // +1 for NUL terminator
59
69 return buf;
70 }
Chapter 12. Manual Memory Allocation 81
71
72 int main(void)
73 {
74 FILE *fp = fopen("foo.txt", "r");
75
76 char *line;
77
83 fclose(fp);
84 }
When growing memory like this, it’s common (though hardly a law) to double the space needed each step
just to minimize the number of realloc()s that occur.
Finally you might note that readline() returns a pointer to a malloc()d buffer. As such, it’s up to the
caller to explicitly free() that memory when it’s done with it.
That could be convenient if you have some kind of allocation loop and you don’t want to special-case the
first malloc().
int *p = NULL;
int length = 0;
while (!done) {
// Allocate 10 more ints:
length += 10;
p = realloc(p, sizeof *p * length);
// Do amazing things
// ...
}
In that example, we didn’t need an initial malloc() since p was NULL to start.
Now, if you use malloc(), calloc(), or realloc(), C will give you a chunk of memory that’s well-aligned
for any value at all, even structs. Works in all cases.
But there might be times that you know that some data can be aligned at a smaller boundary, or must be aligned
at a larger one for some reason. I imagine this is more common with embedded systems programming.
In those cases, you can specify an alignment with aligned_alloc().
The alignment is an integer power of two greater than zero, so 2, 4, 8, 16, etc. and you give that to
aligned_alloc() before the number of bytes you’re interested in.
The other restriction is that the number of bytes you allocate needs to be a multiple of the alignment. But
this might be changing. See C Defect Report 4602
Let’s do an example, allocating on a 64-byte boundary:
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4
5 int main(void)
6 {
7 // Allocate 256 bytes aligned on a 64-byte boundary
8 char *p = aligned_alloc(64, 256); // 256 == 64 * 4
9
I want to throw a note here about realloc() and aligned_alloc(). realloc() doesn’t have any align-
ment guarantees, so if you need to get some aligned reallocated space, you’ll have to do it the hard way with
memcpy().
if (new_ptr == NULL)
return NULL;
if (ptr != NULL)
memcpy(new_ptr, ptr, copy_size);
free(ptr);
return new_ptr;
}
2
http://www.open-std.org/jtc1/sc22/wg14/www/docs/summary.htm#dr_460
Chapter 12. Manual Memory Allocation 83
Note that it always copies data, taking time, while real realloc() will avoid that if it can. So this is hardly
efficient. Avoid needing to reallocate custom-aligned data.
Chapter 13
Scope
3 int main(void)
4 {
5 int a = 12; // Local to outer block, but visible in inner block
6
7 if (a == 12) {
8 int b = 99; // Local to inner block, not visible in outer block
9
1
https://en.wikipedia.org/wiki/Bit_bucket
84
Chapter 13. Scope 85
3 int main(void)
4 {
5 int i = 0;
6
11 int j = 5;
12
Historically, C required all the variables be defined before any code in the block, but this is no longer the
case in the C99 standard.
3 int main(void)
4 {
5 int i = 10;
6
7 {
8 int i = 20;
9
You might have noticed in that example that I just threw a block in there at line 7, not so much as a for or
if statement to kick it off! This is perfectly legal. Sometimes a dev will want to group a bunch of local
variables together for a quick computation and will do this, but it’s rare to see.
For example:
1 #include <stdio.h>
2
3 int shared = 10; // File scope! Visible to the whole file after this!
4
5 void func1(void)
6 {
7 shared += 100; // Now shared holds 110
8 }
9
10 void func2(void)
11 {
12 printf("%d\n", shared); // Prints "110"
13 }
14
15 int main(void)
16 {
17 func1();
18 func2();
19 }
Note that if shared were declared at the bottom of the file, it wouldn’t compile. It has to be declared before
any functions use it.
There are ways to further modify items at file scope, namely with static and extern, but we’ll talk more about
those later.
In that example, i’s lifetime begins the moment it is defined, and continues for the duration of the loop.
If the loop body is enclosed in a block, the variables defined in the for-loop are visible from that inner scope.
Unless, of course, that inner scope hides them. This crazy example prints 999 five times:
1 #include <stdio.h>
2
3 int main(void)
4 {
5 for (int i = 0; i < 5; i++) {
6 int i = 999; // Hides the i in the for-loop scope
7 printf("%d\n", i);
8 }
9 }
Chapter 13. Scope 87
We’re used to char, int, and float types, but it’s now time to take that stuff to the next level and see what
else we have out there in the types department!
Why? Why would you decide you only wanted to hold positive numbers?
Answer: you can get larger numbers in an unsigned variable than you can in a signed ones.
But why is that?
You can think of integers being represented by a certain number of bits1 . On my computer, an int is repre-
sented by 64 bits.
And each permutation of bits that are either 1 or 0 represents a number. We can decide how to divvy up these
numbers.
With signed numbers, we use (roughly) half the permutations to represent negative numbers, and the other
half to represent positive numbers.
With unsigned, we use all the permutations to represent positive numbers.
On my computer with 64-bit ints using two’s complement2 to represent unsigned numbers, I have the fol-
lowing limits on integer range:
1
“Bit” is short for binary digit. Binary is just another way of representing numbers. Instead of digits 0-9 like we’re used to, it’s digits
0-1.
2
https://en.wikipedia.org/wiki/Two%27s_complement
88
Chapter 14. Types II: Way More Types! 89
Notice that the largest positive unsigned int is approximately twice as large as the largest positive int.
So you can get some flexibility there.
Deep down, char is just a small int, namely an integer that uses just a single byte of space, limiting its range
to…
Here the C spec gets just a little funky. It assures us that a char is a single byte, i.e. sizeof(char) == 1.
But then in C11 §3.6¶3 it goes out of its way to say:
A byte is composed of a contiguous sequence of bits, the number of which is implementation-
defined.
Wait—what? Some of you might be used to the notion that a byte is 8 bits, right? I mean, that’s what it
is, right? And the answer is, “Almost certainly.”3 But C is an old language, and machines back in the day
had, shall we say, a more relaxed opinion over how many bits were in a byte. And through the years, C has
retained this flexibility.
But assuming your bytes in C are 8 bits, like they are for virtually all machines in the world that you’ll ever
see, the range of a char is…
—So before I can tell you, it turns out that chars might be signed or unsigned depending on your compiler.
Unless you explicitly specify.
In many cases, just having char is fine because you don’t care about the sign of the data. But if you need
signed or unsigned chars, you must be specific:
char a; // Could be signed or unsigned
signed char b; // Definitely signed
unsigned char c; // Definitely unsigned
OK, now, finally, we can figure out the range of numbers if we assume that a char is 8 bits and your system
uses the virtually universal two’s complement representation for signed and unsigned4 .
So, assuming those constraints, we can finally figure our ranges:
3
The industry term for a sequence of exactly, indisputably 8 bits is an octet.
4
In general, f you have an 𝑛 bit two’s complement number, the signed range is −2𝑛−1 to 2𝑛−1 − 1. And the unsigned range is 0
to 2𝑛 − 1.
Chapter 14. Types II: Way More Types! 90
3 int main(void)
4 {
5 char a = 10, b = 20;
6
What about those constant characters in single quotes, like 'B'? How does that have a numeric value?
The spec is also hand-wavey here, since C isn’t designed to run on a single type of underlying system.
But let’s just assume for the moment that your character set is based on ASCII5 for at least the first 128
characters. In that case, the character constant will be converted to a char whose value is the same as the
ASCII value of the character.
That was a mouthful. Let’s just have an example:
1 #include <stdio.h>
2
3 int main(void)
4 {
5 char a = 10;
6 char b = 'B'; // ASCII value 66
7
This depends on your execution environment and the character set used6 . One of the most popular character
sets today is Unicode7 (which is a superset of ASCII), so for your basic 0-9, A-Z, a-z and punctuation, you’ll
almost certainly get the ASCII values out of them.
But there are a couple more integer types we should look at, and the minimum minimum and maximum values
they can hold.
Yes, I said “minimum” twice. The spec says that these types will hold numbers of at least these sizes, so your
implementation might be different. The header file <limits.h> defines macros that hold the minimum and
maximum integer values; rely on that to be sure, and never hardcode or assume these values.
These additional types are short int, long int, and long long int. Commonly, when using these
types, C developers leave the int part off (e.g. long long), and the compiler is perfectly happy.
// These two lines are equivalent:
long long int x;
long long x;
Let’s take a look at the integer data types and sizes in ascending order, grouped by signedness.
There is no long long long type. You can’t just keep adding longs like that. Don’t be silly.
Two’s complement fans might have noticed something funny about those numbers. Why does,
for example, the signed char stop at -127 instead of -128? Remember: these are only the
minimums required by the spec. Some number representations (like sign and magnitude9 ) top
off at ±127.
Let’s run the same table on my 64-bit, two’s complement system and see what comes out:
8
Depends on if a char defaults to signed char or unsigned char
9
https://en.wikipedia.org/wiki/Signed_number_representations#Signed_magnitude_representation
Chapter 14. Types II: Way More Types! 92
That’s a little more sensible, but we can see how my system has larger limits than the minimums in the
specification.
So what are the macros in <limits.h>?
Notice there’s a way hidden in there to determine if a system uses signed or unsigned chars. If CHAR_MAX
== UCHAR_MAX, it must be unsigned.
Also notice there’s no minimum macro for the unsigned variants—they’re just 0.
Parameter Definition
𝑠 sign (±1)
𝑏 base or radix of exponent representation (an integer
> 1)
𝑒 exponent (an integer between a minimum 𝑒𝑚𝑖𝑛 and
a maximum 𝑒𝑚𝑎𝑥 )
𝑝 precision (the number of base-𝑏 digits in the
significand)
𝑓𝑘 nonnegative integers less than 𝑏 (the significand
digits)
Type sizeof
float 4
double 8
long double 16
So each of the types (on my system) uses those additional bits for more precision.
But how much precision are we talking, here? How many decimal numbers can be represented by these
values?
Well, C provides us with a bunch of macros in <float.h> to help us figure that out.
It gets a little wonky if you are using a base-2 (binary) system for storing the numbers (which is virtually
everyone on the planet, probably including you), but bear with me while we figure it out.
The number of decimal digits you can store in a floating point type and surely get the same number back out
when you print it is given by these macros:
On my system, FLT_DIG is 6, so I can be sure that if I print out a 6 digit float, I’ll get the same thing
back. (It could be more digits—some numbers will come back correctly with more digits. But 6 is definitely
coming back.)
For example, printing out floats following this pattern of increasing digits, we apparently make it to 8 digits
before something goes wrong, but after that we’re back to 7 correct digits.
0.12345
0.123456
0.1234567
0.12345678
0.123456791 <-- Things start going wrong
0.1234567910
Let’s do another demo. In this code we’ll have two floats that both hold numbers that have FLT_DIG
significant decimal digits12 . Then we add those together, for what should be 12 significant decimal digits.
But that’s more than we can store in a float and correctly recover as a string—so we see when we print it
out, things start going wrong after the 7th significant digit.
1 #include <stdio.h>
2 #include <float.h>
3
4 int main(void)
5 {
6 // Both these numbers have 6 significant digits, so they can be
7 // stored accurately in a float:
8
9 float f = 3.14159f;
10 float g = 0.00000265358f;
11
(The above code has an f after the numeric constants—this indicates that the constant is type float, as
opposed to the default of double. More on this later.)
Remember that FLT_DIG is the safe number of digits you can store in a float and retrieve correctly.
Sometimes you might get one or two more out of it. But sometimes you’ll only get FLT_DIG digits back.
12
This program runs as its comments indicate on a system with FLT_DIG of 6 that uses IEEE-754 base-2 floating point numbers.
Otherwise, you might get different output.
Chapter 14. Types II: Way More Types! 95
The sure thing: if you store any number of digits up to and including FLT_DIG in a float, you’re sure to get
them back correctly.
So that’s the story. FLT_DIG. The End.
…Or is it?
But what about those floating point numbers that aren’t in the gap? How many places do you need to print
those out accurately?
Another way to phrase this question is for any given floating point number, how many decimal digits do I
have to preserve if I want to convert the decimal number back into an identical floating point number? That
is, how many digits do I have to print in base 10 to recover all the digits in base 2 in the original number?
Sometimes it might only be a few. But to be sure, you’ll want to convert to decimal with a certain safe number
of decimal places. That number is encoded in the following macros:
Macro Description
FLT_DECIMAL_DIG Number of decimal digits encoded in a float.
DBL_DECIMAL_DIG Number of decimal digits encoded in a double.
LDBL_DECIMAL_DIG Number of decimal digits encoded in a long
double.
DECIMAL_DIG Same as the widest encoding, LDBL_DECIMAL_DIG.
Let’s see an example where DBL_DIG is 15 (so that’s all we can have in a constant), but DBL_DECIMAL_DIG
is 17 (so we have to convert to 17 decimal numbers to preserve all the bits of the original double).
Let’s assign the 15 significant digit number 0.123456789012345 to x, and let’s assign the 1 significant digit
number 0.0000000000000006 to y.
x is exact: 0.12345678901234500 Printed to 17 decimal places
y is exact: 0.00000000000000060
But let’s add them together. This should give 0.1234567890123456, but that’s more than DBL_DIG, so
strange things might happen… let’s look:
x + y not quite right: 0.12345678901234559 Should end in 4560!
That’s what we get for printing more than DBL_DIG, right? But check this out… that number, above, is
exactly representable as it is!
If we assign 0.12345678901234559 (17 digits) to z and print it, we get:
z is exact: 0.12345678901234559 17 digits correct! More than DBL_DIG!
If we’d truncated z down to 15 digits, it wouldn’t have been the same number. That’s why to preserve all the
bits of a double, we need DBL_DECIMAL_DIG and not just the lesser DBL_DIG.
Chapter 14. Types II: Way More Types! 96
All that being said, it’s clear that when we’re messing with decimal numbers in general, it’s not safe to print
more than FLT_DIG, DBL_DIG, or LDBL_DIG digits to be sensible in relation to the original base 10 numbers
and any subsequent math.
But when converting from float to a decimal representation and back to float, definitely use
FLT_DECIMAL_DIG to do that so that all the bits are preserved exactly.
This is particularly problematic for beginner programmers who try to pad decimal numbers on the left with
0 to line things up nice and pretty, inadvertently changing the base of the number:
int x = 11111; // Decimal 11111
int y = 00111; // Decimal 73 (Octal 111)
int z = 01111; // Decimal 585 (Octal 1111)
There’s no printf() format specifier for printing a binary number. You have to do it a character at a time
with bitwise operators.
int x = 1234;
long int x = 1234L;
long long int x = 1234LL
The suffix can be uppercase or lowercase. And the U and L or LL can appear either one first.
Type Suffix
int None
long int L
long long int LL
unsigned int U
unsigned long int UL
unsigned long long int ULL
I mentioned in the table that “no suffix” means int… but it’s actually more complex than that.
So what happens when you have an unsuffixed number like:
int x = 1234;
Octal or Hexadecimal
Suffix Decimal Constant Constant
none int int
long int unsigned int
long int
unsigned long int
long long int
unsigned long long int
Octal or Hexadecimal
Suffix Decimal Constant Constant
l or L long int long int
long long int unsigned long int
long long int
unsigned long long int
What that’s saying is that, for example, if you specify a number like 123456789U, first C will see if it can be
unsigned int. If it doesn’t fit there, it’ll try unsigned long int. And then unsigned long long int.
It’ll use the smallest type that can hold the number.
Type Suffix
float F
double None
long double L
For example:
float x = 3.14f;
double x = 3.14;
long double x = 3.14L;
This whole time, though, we’ve just been doing this, right?
float x = 3.14;
Isn’t the left a float and the right a double? Yes! But C’s pretty good with automatic numeric conversions,
so it’s more common to have an unsuffixed floating point constant than not. More on that later.
However, when you print it, it will change the exponent so there is only one digit in front of the decimal
point.
• The plus can be left off the exponent, as it’s default, but this is uncommon in practice from what I’ve
seen.
1.2345e10 == 1.2345e+10
14
https://en.wikipedia.org/wiki/Scientific_notation
Chapter 15
In this chapter, we want to talk all about converting from one type to another. C has a variety of ways of
doing this, and some might be a little different that you’re used to in other languages.
Before we talk about how to make conversions happen, let’s talk about how they work when they do happen.
These basically work like printf(), except they output to a string instead, and you can print that string later,
or whatever.
For example, turning part of the value π into a string:
1 #include <stdio.h>
2
3 int main(void)
4 {
5 char s[10];
6 float f = 3.14159;
7
100
Chapter 15. Types III: Conversions 101
Function Description
atoi String to int
atof String to float
atol String to long int
atoll String to long long int
Though the spec doesn’t cop to it, the a at the beginning of the function stands for ASCII2 , so really atoi()
is “ASCII-to-integer”, but saying so today is a bit ASCII-centric.
Here’s an example converting a string to a float:
1 #include <stdio.h>
2 #include <stdlib.h>
3
4 int main(void)
5 {
6 char *pi = "3.14159";
7 float f;
8
9 f = atof(pi);
10
11 printf("%f\n", f);
12 }
But, like I said, we get undefined behavior from weird things like this:
int x = atoi("what"); // "What" ain't no number I ever heard of
(When I run that, I get 0 back, but you really shouldn’t count on that in any way. You could get something
completely different.)
For better error handling characteristics, let’s check out all those strtol functions, also in <stdlib.h>. Not
only that, but they convert to more types and more bases, too!
Function Description
strtol String to long int
strtoll String to long long int
strtoul String to unsigned long int
strtoull String to unsigned long long int
strtof String to float
strtod String to double
strtold String to long double
These functions all follow a similar pattern of use, and are a lot of people’s first experience with pointers to
pointers! But never fret—it’s easier than it looks.
2
https://en.wikipedia.org/wiki/ASCII
Chapter 15. Types III: Conversions 102
Let’s do an example where we convert a string to an unsigned long, discarding error information (i.e. in-
formation about bad characters in the input string):
1 #include <stdio.h>
2 #include <stdlib.h>
3
4 int main(void)
5 {
6 char *s = "3490";
7
Notice a couple things there. Even though we didn’t deign to capture any information about error characters
in the string, strtoul() won’t give us undefined behavior; it will just return 0.
Also, we specified that this was a decimal (base 10) number.
Does this mean we can convert numbers of different bases? Sure! Let’s do binary!
1 #include <stdio.h>
2 #include <stdlib.h>
3
4 int main(void)
5 {
6 char *s = "101010"; // What's the meaning of this number?
7
12 printf("%lu\n", x); // 42
13 }
OK, that’s all fun and games, but what’s with that NULL in there? What’s that for?
That helps us figure out if an error occurred in the processing of the string. It’s a pointer to a pointer to a
char, which sounds scary, but isn’t once you wrap your head around it.
Let’s do an example where we feed in a deliberately bad number, and we’ll see how strtol() lets us know
where the first invalid digit is.
1 #include <stdio.h>
2 #include <stdlib.h>
3
4 int main(void)
5 {
6 char *s = "34x90"; // "x" is not a valid digit in base 10!
7 char *badchar;
8
12
15 printf("%lu\n", x); // 34
16
So there we have strtoul() modifying what badchar points to in order to show us where things went
wrong3 .
But what if nothing goes wrong? In that case, badchar will point to the NUL terminator at the end of the
string. So we can test for it:
1 #include <stdio.h>
2 #include <stdlib.h>
3
4 int main(void)
5 {
6 char *s = "3490"; // "x" is not a valid digit in base 10!
7 char *badchar;
8
15 if (*badchar == '\0') {
16 printf("Success! %lu\n", x);
17 } else {
18 printf("Partial conversion: %lu\n", x);
19 printf("Invalid character: %c\n", *badchar);
20 }
21 }
So there you have it. The atoi()-style functions are good in a controlled pinch, but the strtol()-style
functions give you far more control over error handling and the base of the input.
So… no. And 53? What is that? That’s the UTF-8 (and ASCII) code point for the character symbol '5'4
So how do we convert the character '5' (which apparently has value 53) into the value 5?
With one clever trick, that’s how!
The C Standard guarantees that these character will have code points that are in sequence and in this order:
0 1 2 3 4 5 6 7 8 9
You see there that '5' is 53, just like we were getting. And '0' is 48.
So we can subtract '0' from any digit character to get its numeric value:
char c = '6';
And we can convert the other way, too, just by adding the value on.
int x = 6;
You might think this is a weird way to do this conversion, and by today’s standards, it certainly is. But
back in the olden days when computers were made literally out of wood, this was the method for doing this
conversion. And it wasn’t broke, so C never fixed it.
In that case, x and y get promoted to int by C before the math takes place.
The integer promotions take place during The Usual Arithmetic Conversions, with variadic functions8 , unary
+ and - operators, or when passing values to functions without prototypes9 .
If you want to know the gritty details, check out C11 §6.3.1.8. But you probably don’t.
Just generally remember that int types become float types if there’s a floating point type anywhere in there,
and the compiler makes an effort to make sure mixed integer types don’t overflow.
Finally, if you convert from one floating point type to another, the compiler will try to make an exact con-
version. If it can’t, it’ll do the best approximation it can. If the number is too large to fit in the type you’re
converting into, boom: undefined behavior!
15.4.3 void*
The void* type is interesting because it can be converted from or to any pointer type.
int x = 10;
15.5.1 Casting
You can explicitly change the type of an expression by putting a new type in parentheses in front of it. Some
C devs frown on the practice unless absolutely necessary, but it’s likely you’ll come across some C code with
these in it.
Let’s do an example where we want to convert an int into a long so that we can store it in a long.
Note: this example is contrived and the cast in this case is completely unnecessary because the x + 12
expression would automatically be changed to long int to match the wider type of y.
int x = 10;
long int y = (long int)x + 12;
In that example, even those x was type int before, the expression (long int)x has type long int. We
say, “We cast x to long int.”
More commonly, you might see a cast being used to convert a void* into a specific pointer type so it can be
dereferenced.
A callback from the built-in qsort() function might display this behavior since it has void*s passed into it:
int compar(const void *elem1, const void *elem2)
{
if (*((const int*)elem2) > *((const int*)elem1)) return 1;
if (*((const int*)elem2) < *((const int*)elem1)) return -1;
return 0;
}
One place you’ll see casts more commonly is to avoid a warning when printing pointer values with the
rarely-used %p which gets picky with anything other than a void*:
int x = 3490;
int *p = &x;
printf("%p\n", p);
Another place is with explicit pointer changes, if you don’t want to use an intervening void*, but these are
also pretty uncommon:
long x = 3490;
long *p = &x;
unsigned char *c = (unsigned char *)p;
A third place it’s often required is with the character conversion functions in <ctype.h>10 where you should
cast questionably-signed values to unsigned char to avoid undefined behavior.
Again, casting is rarely needed in practice. If you find yourself casting, there might be another way to do the
same thing, or maybe you’re casting unnecessarily.
Or maybe it is necessary. Personally, I try to avoid it, but am not afraid to use it if I have to.
10
https://beej.us/guide/bgclr/html/split/ctype.html
Chapter 16
Now that we have some more types under our belts, turns out we can give these types some additional
attributes that control their behavior. These are the type qualifiers and storage-class specifiers.
16.1.1 const
This is the most common type qualifier you’ll see. It means the variable is constant, and any attempt to
modify it will result in a very angry compiler.
const int x = 2;
108
Chapter 16. Types IV: Qualifiers and Specifiers 109
Great, so we can’t change the thing the pointer points to, but we can change the pointer itself. What if we
want the other way around? We want to be able to change what the pointer points to, but not the pointer
itself?
Just move the const after the asterisk in the declaration:
int *const p; // We can't modify "p" with pointer arithmetic
Finally, if you have multiple levels of indirection, you should const the appropriate levels. Just because a
pointer is const, doesn’t mean the pointer it points to must also be. You can explicitly set them like in the
following examples:
char **p;
p++; // OK!
(*p)++; // OK!
char **const p;
p++; // Error!
(*p)++; // OK!
The compiler is warning us that the value on the right side of the assignment is const, but the one of the
left is not. And the compiler is letting us know that it is discarding the “const-ness” of the expression on the
right.
That is, we can still try to do the following, but it’s just wrong. The compiler will warn, and it’s undefined
behavior:
const int x = 20;
int *p = &x;
16.1.2 restrict
TLDR: you never have to use this and you can ignore it every time you see it. If you use it correctly, you
will likely realize some performance gain. If you use it incorrectly, you will realize undefined behavior.
restrict is a hint to the compiler that a particular piece of memory will only be accessed by one pointer
and never another. (That is, there will be no aliasing of the particular object the restrict pointer points
to.) If a developer declares a pointer to be restrict and then accesses the object it points to in another way
(e.g. via another pointer), the behavior is undefined.
Basically you’re telling C, “Hey—I guarantee that this one single pointer is the only way I access this memory,
and if I’m lying, you can pull undefined behavior on me.”
And C uses that information to perform certain optimizations. For instance, if you’re dereferencing the
restrict pointer repeatedly in a loop, C might decide to cache the result in a register and only store the
final result once the loop completes. If any other pointer referred to that same memory and accessed it in the
loop, the results would not be accurate.
(Note that restrict has no effect if the object pointed to is never written to. It’s all about optimizations
surrounding writes to memory.)
Let’s write a function to swap two variables, and we’ll use the restrict keyword to assure C that we’ll
never pass in pointers to the same thing. And then let’s blow it and try passing in pointers to the same thing.
1 void swap(int *restrict a, int *restrict b)
2 {
3 int t;
4
5 t = *a;
6 *a = *b;
7 *b = t;
8 }
9
10 int main(void)
11 {
12 int x = 10, y = 20;
13
14 swap(&x, &y); // OK! "a" and "b", above, point to different things
Chapter 16. Types IV: Qualifiers and Specifiers 111
15
16 swap(&x, &x); // Undefined behavior! "a" and "b" point to the same thing
17 }
If we were to take out the restrict keywords, above, that would allow both calls to work safely. But then
the compiler might not be able to optimize.
restrict has block scope, that is, the restriction only lasts for the scope it’s used. If it’s in a parameter list
for a function, it’s in the block scope of that function.
If the restricted pointer points to an array, it only applies to the individual objects in the array. Other pointers
could read and write from the array as long as they didn’t read or write any of the same elements as the
restricted one.
If it’s outside any function in file scope, the restriction covers the entire program.
You’re likely to see this in library functions like printf():
int printf(const char * restrict format, ...);
Again, that’s just telling the compiler that inside the printf() function, there will be only one pointer that
refers to any part of that format string.
One last note: if you’re using array notation in your function parameter for some reason instead of pointer
notation, you can use restrict like so:
void foo(int p[restrict]) // With no size
16.1.3 volatile
You’re unlikely to see or need this unless you’re dealing with hardware directly.
volatile tells the compiler that a value might change behind its back and should be looked up every time.
An example might be where the compiler is looking in memory at an address that continuously updates
behind the scenes, e.g. some kind of hardware timer.
If the compiler decides to optimize that and store the value in a register for a protracted time, the value in
memory will update and won’t be reflected in the register.
By declaring something volatile, you’re telling the compiler, “Hey, the thing this points at might change
at any time for reasons outside this program code.”
volatile int *p;
16.1.4 _Atomic
This is an optional C feature that we’ll talk about in the Atomics chapter.
16.2.1 auto
You barely ever see this keyword, since auto is the default for block scope variables. It’s implied.
These are the same:
{
int a; // auto is the default...
auto int a; // So this is redundant
}
The auto keyword indicates that this object has automatic storage duration. That is, it exists in the scope in
which it is defined, and is automatically deallocated when the scope is exited.
One gotcha about automatic variables is that their value is indeterminate until you explicitly initialize them.
We say they’re full of “random” or “garbage” data, though neither of those really makes me happy. In any
case, you won’t know what’s in it unless you initialize it.
Always initialize all automatic variables before use!
16.2.2 static
This keyword has two meanings, depending on if the variable is file scope or block scope.
Let’s start with block scope.
3 void counter(void)
4 {
5 static int count = 1; // This is initialized one time
6
9 count++;
10 }
11
12 int main(void)
13 {
14 counter(); // "This has been called 1 time(s)"
15 counter(); // "This has been called 2 time(s)"
16 counter(); // "This has been called 3 time(s)"
17 counter(); // "This has been called 4 time(s)"
18 }
Finally, be advised that if you’re writing multithreaded programs, you have to be sure you don’t let multiple
threads trample the same variable.
16.2.3 extern
The extern storage-class specifier gives us a way to refer to objects in other source files.
Let’s say, for example, the file bar.c had the following as its entirety:
1 // bar.c
2
3 int a = 37;
3 extern int a;
4
5 int main(void)
6 {
7 printf("%d\n", a); // 37, from bar.c!
8
9 a = 99;
10
We could have also made the extern int a in block scope, and it still would have referred to the a in
bar.c:
1 // foo.c
2
3 int main(void)
4 {
5 extern int a;
6
9 a = 99;
10
Chapter 16. Types IV: Qualifiers and Specifiers 114
Now, if a in bar.c had been marked static. this wouldn’t have worked. static variables at file scope are
not visible outside that file.
A final note about extern on functions. For functions, extern is the default, so it’s redundant. You can
declare a function static if you only want it visible in a single source file.
16.2.4 register
This is a keyword to hint to the compiler that this variable is frequently-used, and should be made as fast as
possible to access. The compiler is under no obligation to agree to it.
Now, modern C compiler optimizers are pretty effective at figuring this out themselves, so it’s rare to see
these days.
But if you must:
1 #include <stdio.h>
2
3 int main(void)
4 {
5 register int a; // Make "a" as fast to use as possible.
6
It does come at a price, however. You can’t take the address of a register:
register int a;
int *p = &a; // COMPILER ERROR! Can't take address of a register
Interestingly, for the equivalent with array notation, gcc only warns:
register int a[] = {11, 22, 33, 44, 55};
with:
warning: ISO C forbids subscripting ‘register’ array
The fact that you can’t take the address of a register variable frees the compiler up to make optimizations
around that assumption if it hasn’t figured them out already. Also adding register to a const variable
prevents one from accidentally passing its pointer to another function that willfully ignore its constness1 .
1
https://gustedt.wordpress.com/2010/08/17/a-common-misconsception-the-register-keyword/
Chapter 16. Types IV: Qualifiers and Specifiers 115
A bit of historic backstory, here: deep inside the CPU are little dedicated “variables” called registers2 . They
are super fast to access compared to RAM, so using them gets you a speed boost. But they’re not in RAM,
so they don’t have an associated memory address (which is why you can’t take the address-of or get a pointer
to them).
But, like I said, modern compilers are really good at producing optimal code, using registers whenever
possible regardless of whether or not you specified the register keyword. Not only that, but the spec
allows them to just treat it as if you’d typed auto, if they want. So no guarantees.
16.2.5 _Thread_local
When you’re using multiple threads and you have some variables in either global or static block scope,
this is a way to make sure that each thread gets its own copy of the variable. This’ll help you avoid race
conditions and threads stepping on each other’s toes.
If you’re in block scope, you have to use this along with either extern or static.
Also, if you include <threads.h>, you can use the rather more palatable thread_local as an alias for the
uglier _Thread_local.
More information can be found in the Threads section.
2
https://en.wikipedia.org/wiki/Processor_register
Chapter 17
Multifile Projects
So far we’ve been looking at toy programs that for the most part fit in a single file. But complex C programs
are made up of many files that are all compiled and linked together into a single executable.
In this chapter we’ll check out some of the common patterns and practices for putting together larger projects.
In that examples, foo.c and bar.c get built into the executable named foo.
So let’s take a look at the source file bar.c:
1 // File bar.c
2
3 #include <stdio.h>
4
5 int main(void)
6 {
116
Chapter 17. Multifile Projects 117
See how from main() we call add()—but add() is in a completely different source file! It’s in bar.c,
while the call to it is in foo.c!
If we build this with:
gcc -o foo foo.c bar.c
(Or you might get a warning. Which you should not ignore. Never ignore warnings in C; address them all.)
If you recall from the section on prototypes, implicit declarations are banned in modern C and there’s no
legitimate reason to introduce them into new code. We should fix it.
What implicit declaration means is that we’re using a function, namely add() in this case, without
letting C know anything about it ahead of time. C wants to know what it returns, what types it takes as
arguments, and things such as that.
We saw how to fix that earlier with a function prototype. Indeed, if we add one of those to foo.c before we
make the call, everything works well:
1 // File foo.c
2
3 #include <stdio.h>
4
7 int main(void)
8 {
9 printf("%d\n", add(2, 3)); // 5!
10 }
No more error!
But that’s a pain—needing to type in the prototype every time you want to use a function. I mean, we used
printf() right there and didn’t need to type in a prototype; what gives?
If you remember from what back with hello.c at the beginning of the book, we actually did include the
prototype for printf()! It’s in the file stdio.h! And we included that with #include!
Can we do the same with our add() function? Make a prototype for it and put it in a header file?
Sure!
Header files in C have a .h extension by default. And they often, but not always, have the same name as
their corresponding .c file. So let’s make a bar.h file for our bar.c file, and we’ll stick the prototype in it:
1 // File bar.h
2
And now let’s modify foo.c to include that file. Assuming it’s in the same directory, we include it inside
double quotes (as opposed to angle brackets):
1 // File foo.c
2
3 #include <stdio.h>
Chapter 17. Multifile Projects 118
7 int main(void)
8 {
9 printf("%d\n", add(2, 3)); // 5!
10 }
Notice how we don’t have the prototype in foo.c anymore—we included it from bar.h. Now any file that
wants that add() functionality can just #include "bar.h" to get it, and you don’t need to worry about
typing in the function prototype.
As you might have guessed, #include literally includes the named file right there in your source code, just
as if you’d typed it in.
And building and running:
./foo
5
What we need to do is make it so that if a file gets included once, subsequent #includes for that file are
ignored.
The stuff that we’re about to do is so common that you should just automatically do it every time you
make a header file!
And the common way to do this is with a preprocessor variable that we set the first time we #include the
file. And then for subsequent #includes, we first check to make sure that the variable isn’t defined.
For that variable name, it’s super common to take the name of the header file, like bar.h, make it uppercase,
and replace the period with an underscore: BAR_H.
So put a check at the very, very top of the file where you see if it’s already been included, and effectively
comment the whole thing out if it has.
(Don’t put a leading underscore (because a leading underscore followed by a capital letter is reserved) or a
double leading underscore (because that’s also reserved.))
1 #ifndef BAR_H // If BAR_H isn't defined...
2 #define BAR_H // Define it (with no particular value)
3
Chapter 17. Multifile Projects 119
4 // File bar.h
5
This will effectively cause the header file to be included only a single time, no matter how many places try
to #include it.
Voila, we’ve produced an executable foo from the two object files.
But you’re thinking, why bother? Can’t we just:
gcc -o foo foo.c bar.c
When you run a program, it’s actually you talking to the shell, saying, “Hey, please run this thing.” And the
shell says, “Sure,” and then tells the operating system, “Hey, could you please make a new process and run
this thing?” And if all goes well, the OS complies and your program runs.
But there’s a whole world outside your program in the shell that can be interacted with from within C. We’ll
look at a few of those in this chapter.
120
Chapter 18. The Outside Environment 121
arg 0: ./foo
arg 1: i
arg 2: like
arg 3: turtles
It’s a little weird, because the zeroth argument is the name of the executable, itself. But that’s just something
to get used to. The arguments themselves follow directly.
Source:
1 #include <stdio.h>
2
Whoa! What’s going on with the main() function signature? What’s argc and argv2 (pronounced arg-cee
and arg-vee)?
Let’s start with the easy one first: argc. This is the argument count, including the program name, itself. If
you think of all the arguments as an array of strings, which is exactly what they are, then you can think of
argc as the length of that array, which is exactly what it is.
And so what we’re doing in that loop is going through all the argvs and printing them out one at a time, so
for a given input:
./foo i like turtles
2
Since they’re just regular parameter names, you don’t actually have to call them argc and argv. But it’s so very idiomatic to use
those names, if you get creative, other C programmers will look at you with a suspicious eye, indeed!
Chapter 18. The Outside Environment 122
11 total += value;
12 }
13
14 printf("%d\n", total);
15 }
Sample runs:
$ ./add
0
$ ./add 1
1
$ ./add 1 2
3
$ ./add 1 2 3
6
$ ./add 1 2 3 4
10
Of course, it might puke if you pass in a non-integer, but hardening against that is left as an exercise to the
reader.
is always true!
This might seem pointless, but it turns out to be useful in a couple places; we’ll take a look at one of those
right now.
Now, it’s been convenient to think of argv as an array of strings, i.e. an array of char*s, so this made sense:
int main(int argc, char *argv[])
Yeah, that’s a pointer to a pointer, all right! If it makes it easier, think of it as a pointer to a string. But really,
it’s a pointer to a value that points to a char.
Also recall that these are equivalent:
Chapter 18. The Outside Environment 123
argv[i]
*(argv + i)
15 total += value;
16 }
17
18 printf("%d\n", total);
19 }
Personally, I use array notation to access argv, but have seen this style floating around, as well.
But if you modify argv[0] like so, being careful that the new string "Hi! " is the same length as
the old one "./foo":
strcpy(argv[0], "Hi! ");
3
ps, Process Status, is a Unix command to see what processes are running at the moment.
Chapter 18. The Outside Environment 124
and then run ps while the program ./foo is still executing, we’ll see this instead:
4079 tty1 S 0:00 Hi!
Status Description
EXIT_SUCCESS or 0 Program terminated successfully.
EXIT_FAILURE Program terminated with an error.
Let’s write a short program that multiplies two numbers from the command line. We’ll require that you
specify exactly two values. If you don’t, we’ll print an error message, and exit with an error status.
4
https://en.wikipedia.org/wiki/Inception
5
https://en.wikipedia.org/wiki/Shell_(computing)
Chapter 18. The Outside Environment 125
1 #include <stdio.h>
2 #include <stdlib.h>
3
Now if we try to run this, we get the expected effect until we specify exactly the right number of command-
line arguments:
$ ./mult
usage: mult x y
$ ./mult 3 4 5
usage: mult x y
$ ./mult 3 4
12
But that doesn’t really show the exit status that we returned, does it? We can get the shell to print it out,
though. Assuming you’re running Bash or another POSIX shell, you can use echo $? to see it6 .
Let’s try:
$ ./mult
usage: mult x y
$ echo $?
1
$ ./mult 3 4 5
usage: mult x y
$ echo $?
1
$ ./mult 3 4
12
$ echo $?
0
Interesting! We see that on my system, EXIT_FAILURE is 1. The spec doesn’t spell this out, so it could be
any number. But try it; it’s probably 1 on your system, too.
number means failure. So you can only have one type of success, and multiple types of failure. Bash says
the exit code should be between 0 and 255, though a number of codes are reserved.
In short, if you want to indicate different error exit statuses in a Unix environment, you can start with 1 and
work your way up.
On Linux, if you try any code outside the range 0-255, it will bitwise AND the code with 0xff, effectively
clamping it to that range.
You can script the shell to later use these status codes to make decisions about what to do next.
Notice they are in the form of key/value pairs. For example, one key is HOSTTYPE and its value is x86_64.
From a C perspective, all values are strings, even if they’re numbers7 .
So, anyway! Long story short, it’s possible to get these values from inside your C program.
Let’s write a program that uses the standard getenv() function to look up a value that you set in the shell.
getenv() will return a pointer to the value string, or else NULL if the environment variable doesn’t exist.
1 #include <stdio.h>
2 #include <stdlib.h>
3
4 int main(void)
5 {
6 char *val = getenv("FROTZ"); // Try to get the value
7
$ ./foo
Cannot find the FROTZ environment variable
In this way, you can set up data in environment variables, and you can get it in your C code and modify your
behavior accordingly.
5 int main(void)
6 {
7 for (char **p = environ; *p != NULL; p++) {
8 printf("%s\n", *p);
9 }
10
8
In Windows CMD.EXE, use set FROTZ=value. In PowerShell, use $Env:FROTZ=value.
Chapter 18. The Outside Environment 128
Use getenv() if at all possible because it’s more portable. But if you have to iterate over environment
variables, using environ might be the way to go.
Another non-standard way to get the environment variables is as a parameter to main(). It works much the
same way, but you avoid needing to add your extern environ variable. Not even the POSIX spec supports
this9 as far as I can tell, but it’s common in Unix land.
1 #include <stdio.h>
2
Just like using environ but even less portable. It’s good to have goals.
9
https://pubs.opengroup.org/onlinepubs/9699919799/functions/exec.html
Chapter 19
The C Preprocessor
Before your program gets compiled, it actually runs through a phase called preprocessing. It’s almost like
there’s a language on top of the C language that runs first. And it outputs the C code, which then gets
compiled.
We’ve already seen this to an extent with #include! That’s the C Preprocessor! Where it sees that directive,
it includes the named file right there, just as if you’d typed it in there. And then the compiler builds the whole
thing.
But it turns out it’s a lot more powerful than just being able to include things. You can define macros that
are substituted… and even macros that take arguments!
19.1 #include
Let’s start with the one we’ve already seen a bunch. This is, of course, a way to include other sources in your
source. Very commonly used with header files.
While the spec allows for all kinds of behavior with #include, we’re going to take a more pragmatic ap-
proach and talk about the way it works on every system I’ve ever seen.
We can split header files into two categories: system and local. Things that are built-in, like stdio.h,
stdlib.h, math.h, and so on, you can include with angle brackets:
#include <stdio.h>
#include <stdlib.h>
The angle brackets tell C, “Hey, don’t look in the current directory for this header file—look in the system-
wide include directory instead.”
Which, of course, implies that there must be a way to include local files from the current directory. And there
is: with double quotes:
#include "myheader.h"
Or you can very probably look in relative directories using forward slashes and dots, like this:
#include "mydir/myheader.h"
#include "../someheader.py"
Don’t use a backslash (\) for your path separators in your #include! It’s undefined behavior! Use forward
slash (/) only, even on Windows.
129
Chapter 19. The C Preprocessor 130
In summary, used angle brackets (< and >) for the system includes, and use double quotes (") for your personal
includes.
6 int main(void)
7 {
8 printf("%s, %f\n", HELLO, PI);
9 }
On lines 3 and 4 we defined a couple macros. Wherever these appear elsewhere in the code (line 8), they’ll
be substituted with the defined values.
From the C compiler’s perspective, it’s exactly as if we’d written this, instead:
1 #include <stdio.h>
2
3 int main(void)
4 {
5 printf("%s, %f\n", "Hello, world", 3.14159);
6 }
See how HELLO was replaced with "Hello, world" and PI was replaced with 3.14159? From the com-
piler’s perspective, it’s just like those values had appeared right there in the code.
Note that the macros don’t have a specific type, per se. Really all that happens is they get replaced wholesale
with whatever they’re #defined as. If the resulting C code is invalid, the compiler will puke.
You can also define a macro with no value:
#define EXTRA_HAPPY
in that case, the macro exists and is defined, but is defined to be nothing. So anyplace it occurs in the text
will just be replaced with nothing. We’ll see a use for this later.
It’s conventional to write macro names in ALL_CAPS even though that’s not technically required.
Overall, this gives you a way to define constant values that are effectively global and can be used any place.
Even in those places where a const variable won’t work, e.g. in switch cases and fixed array lengths.
That said, the debate rages online whether a typed const variable is better than #define macro in the general
case.
It can also be used to replace or modify keywords, a concept completely foreign to const, though this practice
should be used sparingly.
Chapter 19. The C Preprocessor 131
3 #define EXTRA_HAPPY
4
5 int main(void)
6 {
7
8 #ifdef EXTRA_HAPPY
9 printf("I'm extra happy!\n");
10 #endif
11
12 printf("OK!\n");
13 }
In that example, we define EXTRA_HAPPY (to be nothing, but it is defined), then on line 8 we check to see
if it is defined with an #ifdef directive. If it is defined, the subsequent code will be included up until the
#endif.
So because it is defined, the code will be included for compilation and the output will be:
I'm extra happy!
OK!
then it wouldn’t be defined, and the code wouldn’t be included in compilation. And the output would just
be:
OK!
It’s important to remember that these decisions happen at compile time! The code actually gets compiled or
removed depending on the condition. This is in contrast to a standard if statement that gets evaluated while
the program is running.
12 #ifndef EXTRA_HAPPY
13 printf("I'm just regular\n");
14 #endif
Chapter 19. The C Preprocessor 132
int x = 12;
This demonstrates how a macro persists across files and multiple #includes. If it’s not yet defined, let’s
define it and compile the whole header file.
But the next time it’s included, we see that MYHEADER_H is defined, so we don’t send the header file to the
compiler—it gets effectively removed.
19.3.3 #else
But that’s not all we can do! There’s also an #else that we can throw in the mix.
Let’s mod the previous example:
8 #ifdef EXTRA_HAPPY
9 printf("I'm extra happy!\n");
10 #else
11 printf("I'm just regular\n");
12 #endif
Now if EXTRA_HAPPY is not defined, it’ll hit the #else clause and print:
I'm just regular
3 #define HAPPY_FACTOR 1
4
5 int main(void)
6 {
7
8 #if HAPPY_FACTOR == 0
9 printf("I'm not happy!\n");
10 #elif HAPPY_FACTOR == 1
11 printf("I'm just regular\n");
12 #else
13 printf("I'm extra happy!\n");
14 #endif
15
Chapter 19. The C Preprocessor 133
16 printf("OK!\n");
17 }
Again, for the unmatched #if clauses, the compiler won’t even see those lines. For the above code, after the
preprocessor gets finished with it, all the compiler sees is:
1 #include <stdio.h>
2
3 int main(void)
4 {
5
8 printf("OK!\n");
9 }
One hackish thing this is used for is to comment out large numbers of lines quickly1 .
If you put an #if 0 (“if false”) at the front of the block to be commented out and an #endif at the end, you
can get this effect:
#if 0
printf("All this code"); /* is effectively */
printf("commented out"); // by the #if 0
#endif
You might have noticed that there’s no #elifdef or #elifndef directives. How can we get the same effect
with #if? That is, what if I wanted this:
#ifdef FOO
x = 2;
#elifdef BAR // ERROR: Not supported by standard C
x = 3;
#endif
As are these:
#ifndef FOO
#if !defined FOO
#if !defined(FOO) // Parentheses optional
Notice how we can use the standard logical NOT operator (!) for “not defined”.
So now we’re back in #if land and we can use #elif with impunity!
This broken code:
#ifdef FOO
x = 2;
#elifdef BAR // ERROR: Not supported by standard C
1
You can’t always just wrap the code in /* */ comments because those won’t nest.
Chapter 19. The C Preprocessor 134
x = 3;
#endif
3 int main(void)
4 {
5 #define GOATS
6
7 #ifdef GOATS
8 printf("Goats detected!\n"); // prints
9 #endif
10
13 #ifdef GOATS
14 printf("Goats detected, again!\n"); // doesn't print
15 #endif
16 }
Macro Description
__DATE__ The date of compilation—like when you’re
compiling this file—in Mmm dd yyyy format
__TIME__ The time of compilation in hh:mm:ss format
__FILE__ A string containing this file’s name
__LINE__ The line number of the file this macro appears on
__func__ The name of the function this appears in, as a string2
__STDC__ Defined with 1 if this is a standard C compiler
2
This isn’t really a macro—it’s technically an identifier. But it’s the only predefined identifier and it feels very macro-like, so I’m
including it here. Like a rebel.
Chapter 19. The C Preprocessor 135
Macro Description
__STDC_HOSTED__ This will be 1 if the compiler is a hosted
implementation3 , otherwise 0
__STDC_VERSION__ This version of C, a constant long int in the form
yyyymmL, e.g. 201710L
3 int main(void)
4 {
5 printf("This function: %s\n", __func__);
6 printf("This file: %s\n", __FILE__);
7 printf("This line: %d\n", __LINE__);
8 printf("Compiled on: %s %s\n", __DATE__, __TIME__);
9 printf("C Version: %ld\n", __STDC_VERSION__);
10 }
__FILE__, __func__ and __LINE__ are particularly useful to report error conditions in messages to devel-
opers. The assert() macro in <assert.h> uses these to call out where in the code the assertion failed.
19.4.1.1 __STDC_VERSION__s
In case you’re wondering, here are the version numbers for different major releases of the C Language Spec:
3
A hosted implementation basically means you’re running the full C standard, probably on an operating system of some kind. Which
you probably are. If you’re running on bare metal in some kind of embedded system, you’re probably on a standalone implementation.
Chapter 19. The C Preprocessor 136
Macro Description
__STDC_ISO_10646__ If defined, wchar_t holds Unicode values,
otherwise something else
__STDC_MB_MIGHT_NEQ_WC__ A 1 indicates that the values in multibyte characters
might not map equally to values in wide characters
__STDC_UTF_16__ A 1 indicates that the system uses UTF-16 encoding
in type char16_t
__STDC_UTF_32__ A 1 indicates that the system uses UTF-32 encoding
in type char32_t
__STDC_ANALYZABLE__ A 1 indicates the code is analyzable4
__STDC_IEC_559__ 1 if IEEE-754 (aka IEC 60559) floating point is
supported
__STDC_IEC_559_COMPLEX__ 1 if IEC 60559 complex floating point is supported
__STDC_LIB_EXT1__ 1 if this implementation supports a variety of “safe”
alternate standard library functions (they have _s
suffixes on the name)
__STDC_NO_ATOMICS__ 1 if this implementation does not support _Atomic
or <stdatomic.h>
__STDC_NO_COMPLEX__ 1 if this implementation does not support complex
types or <complex.h>
__STDC_NO_THREADS__ 1 if this implementation does not support
<threads.h>
__STDC_NO_VLA__ 1 if this implementation does not support
variable-length arrays
5 int main(void)
6 {
7 printf("%d\n", SQR(12)); // 144
8 }
What that’s saying is “everywhere you see SQR with some value, replace it with that value times itself”.
So line 7 will be changed to:
4
OK, I know that was a cop-out answer. Basically there’s an optional extension compilers can implement wherein they agree to limit
certain types of undefined behavior so that the C code is more amenable to static code analysis. It is unlikely you’ll need to use this.
Chapter 19. The C Preprocessor 137
What happened?
If we follow the macro expansion, we get
7 printf("%d\n", 3 + 4 * 3 + 4); // 19!
Oops! Since multiplication takes precedence, we do the 4 × 3 = 12 first, and get 3 + 12 + 4 = 19. Not
what we were after.
So we have to fix this to make it right.
This is so common that you should automatically do it every time you make a parameterized math
macro!
The fix is easy: just add some parentheses!
3 #define SQR(x) (x) * (x) // Better... but still not quite good enough!
But we actually still have the same problem which might manifest if we have a higher-precedence operator
than multiply (*) nearby.
So the safe, proper way to put the macro together is to wrap the whole thing in additional parentheses, like
so:
3 #define SQR(x) ((x) * (x)) // Good!
Just make it a habit to do that when you make a math macro and you can’t go wrong.
Let’s do some macros that solve for 𝑥 using the quadratic formula. Just in case you don’t have it on the top
of your head, it says for equations of the form:
𝑎𝑥2 + 𝑏𝑥 + 𝑐 = 0
you can solve for 𝑥 with the quadratic formula:
√
−𝑏 ± 𝑏2 − 4𝑎𝑐
𝑥=
2𝑎
Which is crazy. Also notice the plus-or-minus (±) in there, indicating that there are actually two solutions.
So let’s make macros for both:
Chapter 19. The C Preprocessor 138
So that gets us some math. But let’s define one more that we can use as arguments to printf() to print both
answers.
// macro replacement
// |-----------| |----------------------------|
#define QUAD(a, b, c) QUADP(a, b, c), QUADM(a, b, c)
That’s just a couple values separated by a comma—and we can use that as a “combined” argument of sorts
to printf() like this:
printf("x = %f or x = %f\n", QUAD(2, 10, 5));
8 int main(void)
9 {
10 printf("2*x^2 + 10*x + 5 = 0\n");
11 printf("x = %f or x = %f\n", QUAD(2, 10, 5));
12 }
Plugging in either of those values gives us roughly zero (a bit off because the numbers aren’t exact):
2 × −0.5635082 + 10 × −0.563508 + 5 ≈ 0.000003
8 int main(void)
9 {
10 printf("%d %f %s %d\n", X(5, 4, 3.14, "Hi!", 12));
11 }
for output:
130 3.140000 Hi! 12
19.5.4 Stringification
Already mentioned, just above, you can turn any argument into a string by preceding it with a # in the
replacement text.
For example, we could print anything as a string with this macro and printf():
#define STR(x) #x
printf("%s\n", STR(3.14159));
Let’s see if we can use this to greater effect so that we can pass any int variable name into a macro, and
have it print out it’s name and value.
1 #include <stdio.h>
2
5 int main(void)
6 {
7 int a = 5;
8
19.5.5 Concatenation
We can concatenate two arguments together with ##, as well. Fun times!
#define CAT(a, b) a ## b
1 #include <stdio.h>
2
3 #define PRINT_NUMS_TO_PRODUCT(a, b) do { \
4 int product = (a) * (b); \
5 for (int i = 0; i < product; i++) { \
6 printf("%d\n", i); \
7 } \
8 } while(0)
9
10 int main(void)
11 {
12 PRINT_NUMS_TO_PRODUCT(2, 4); // Outputs numbers from 0 to 7
13 }
5 int main(void)
6 {
7 int i = 0;
8
9 if (i == 0)
10 FOO(i);
11 else
12 printf(":-(\n");
13
14 printf("%d\n", i);
15 }
if (i == 0) {
(i)++;
}; // <-- Trouble with a capital-T!
else
printf(":-(\n");
Chapter 19. The C Preprocessor 141
The ; puts an end to the if statement, so the else is just floating out there illegally5 .
So wrap that multiline macro with a do-while(0).
I want something like this to happen (assuming the ASSERT() is on line 220 of foo.c):
if (!(x < 20)) {
fprintf(stderr, "foo.c:220: assertion x < 20 failed: ");
fprintf(stderr, "x must be under 20\n");
exit(1);
}
We can get the filename out of the __FILE__ macro, and the line number from __LINE__. The message is
already a string, but x < 20 is not, so we’ll have to stringify it with #. We can make a multiline macro by
using backslash escapes at the end of the line.
#define ASSERT(c, m) \
do { \
if (!(c)) { \
fprintf(stderr, __FILE__ ":%d: assertion %s failed: %s\n", \
__LINE__, #c, m); \
exit(1); \
} \
} while(0)
(It looks a little weird with __FILE__ out front like that, but remember it is a string literal, and string literals
next to each other are automagically concatenated. __LINE__ on the other hand, it’s just an int.)
And that works! If I run this:
int x = 30;
Very nice!
The only thing left is a way to turn it on and off, and we could do that with conditional compilation.
Here’s the complete example:
5
Breakin’ the law… breakin’ the law…
Chapter 19. The C Preprocessor 142
1 #include <stdio.h>
2 #include <stdlib.h>
3
4 #define ASSERT_ENABLED 1
5
6 #if ASSERT_ENABLED
7 #define ASSERT(c, m) \
8 do { \
9 if (!(c)) { \
10 fprintf(stderr, __FILE__ ":%d: assertion %s failed: %s\n", \
11 __LINE__, #c, m); \
12 exit(1); \
13 } \
14 } while(0)
15 #else
16 #define ASSERT(c, m) // Empty macro if not enabled
17 #endif
18
19 int main(void)
20 {
21 int x = 30;
22
Some compilers have a non-standard complementary #warning directive that will output a warning but not
stop compilation, but this is not in the C11 spec.
There are all kinds of #pragma directives documented across all four corners of the globe.
All unrecognized #pragmas are ignored by the compiler.
For example:
#pragma STDC FP_CONTRACT OFF
#pragma STDC CX_LIMITED_RANGE ON
which is just cosmetic; the line with the solitary # can be deleted with no ill effect.
Or maybe for cosmetic consistency, like this:
#
#ifdef FOO
x = 2;
#endif
#
#if BAR == 17
x = 12;
#endif
#
Turns out there’s a lot more you can do with structs than we’ve talked about, but it’s just a big pile of
miscellaneous things. So we’ll throw them in this chapter.
If you’re good with struct basics, you can round out your knowledge here.
Turns out we have more power in these initializers than we’d originally shared. Exciting!
For one thing, if you have a nested substructure like the following, you can initialize members of that sub-
structure by following the variable names down the line:
struct foo x = {.a.b.c=12};
3 struct cabin_information {
4 int window_count;
5 int o2level;
6 };
7
8 struct spaceship {
9 char *manufacturer;
10 struct cabin_information ci;
11 };
12
13 int main(void)
14 {
15 struct spaceship s = {
16 .manufacturer="General Products",
17 .ci.window_count = 8, // <-- NESTED INITIALIZER!
18 .ci.o2level = 21
19 };
20
145
Chapter 20. structs II: More Fun with structs 146
Check out lines 16-17! That’s where we’re initializing members of the struct cabin_information in the
definition of s, our struct spaceship.
And here is another option for that same initializer—this time we’ll do something more standard-looking,
but either approach works:
15 struct spaceship s = {
16 .manufacturer="General Products",
17 .ci={
18 .window_count = 8,
19 .o2level = 21
20 }
21 };
Now, as if the above information isn’t spectacular enough, we can also mix in array initializers in there, too.
Let’s change this up to get an array of passenger information in there, and we can check out how the initializers
work in there, too.
1 #include <stdio.h>
2
3 struct passenger {
4 char *name;
5 int covid_vaccinated; // Boolean
6 };
7
8 #define MAX_PASSENGERS 8
9
10 struct spaceship {
11 char *manufacturer;
12 struct passenger passenger[MAX_PASSENGERS];
13 };
14
15 int main(void)
16 {
17 struct spaceship s = {
18 .manufacturer="General Products",
19 .passenger = {
20 // Initialize a field at a time
21 [0].name = "Gridley, Lewis",
22 [0].covid_vaccinated = 0,
23
24 // Or all at once
25 [7] = {.name="Brown, Teela", .covid_vaccinated=1},
26 }
27 };
28
34 s.passenger[i].name,
35 s.passenger[i].covid_vaccinated? "": "not ");
36 }
Okaaaaay. So we have a struct, but it has no name, so we have no way of using it later? Seems pretty
pointless.
Admittedly, in that example, it is. But we can still make use of it a couple ways.
One is rare, but since the anonymous struct represents a type, we can just put some variable names after it
and use them.
struct { // <-- No name!
char *name;
int leg_count, speed;
} a, b, c; // 3 variables of this struct type
a.name = "antelope";
c.leg_count = 4; // for example
animal a, b, c;
a.name = "antelope";
c.leg_count = 4; // for example
Personally, I don’t use many anonymous structs. I think it’s more pleasant to see the entire struct animal
before the variable name in a declaration.
But that’s just, like, my opinion, man.
Chapter 20. structs II: More Fun with structs 148
It’s important to note that next is a pointer. This is what allows the whole thing to even build. Even though
the compiler doesn’t know what the entire struct node looks like yet, all pointers are the same size.
Here’s a cheesy linked list program to test it out:
1 #include <stdio.h>
2 #include <stdlib.h>
3
4 struct node {
5 int data;
6 struct node *next;
7 };
8
9 int main(void)
10 {
11 struct node *head;
12
22 // Traverse it
23 for (struct node *cur = head; cur != NULL; cur = cur->next) {
24 printf("%d\n", cur->data);
25 }
26 }
I want to be clear that the first part of the section is the old way of doing things, and we’re going to do things
the new way after that.
For example, maybe you could define a struct for holding strings and the length of that string. It would
have a length and an array to hold the data. Maybe something like this:
struct len_string {
int length;
char data[8];
};
But that has 8 hardcoded as the maximum length of a string, and that’s not much. What if we did something
clever and just malloc()d some extra space at the end after the struct, and then let the data overflow into
that space?
Let’s do that, and then allocate another 40 bytes on top of it:
struct len_string *s = malloc(sizeof *s + 40);
Because data is the last field of the struct, if we overflow that field, it runs out into space that we already
allocated! For this reason, this trick only works if the short array is the last field in the struct.
// Copy more than 8 bytes!
In fact, there was a common compiler workaround for doing this, where you’d allocate a zero length array at
the end:
struct len_string {
int length;
char data[0];
};
And then every extra byte you allocated was ready for use in that string.
Because data is the last field of the struct, if we overflow that field, it runs out into space that we already
allocated!
// Copy more than 8 bytes!
But, of course, actually accessing the data beyond the end of that array is undefined behavior! In these
modern times, we no longer deign to resort to such savagery.
Luckily for us, we can still get the same effect with C99 and later, but now it’s legal.
Let’s just change our above definition to have no size for the array1 :
struct len_string {
int length;
char data[];
};
Again, this only works if the flexible array member is the last field in the struct.
And then we can allocate all the space we want for those strings by malloc()ing larger than the struct
len_string, as we do in this example that makes a new struct len_string from a C string:
1
Technically we say that it has an incomplete type.
Chapter 20. structs II: More Fun with structs 150
ls->length = len;
return ls;
}
3 struct foo {
4 int a;
5 char b;
6 int c;
7 char d;
8 };
9
10 int main(void)
11 {
12 printf("%zu\n", sizeof(int) + sizeof(char) + sizeof(int) + sizeof(char));
13 printf("%zu\n", sizeof(struct foo));
14 }
They’re not the same! The compiler has added 6 bytes of padding to help it be more performant. Maybe you
got different output with your compiler, but unless you’re forcing it, you can’t be sure there’s no padding.
20.6 offsetof
In the previous section, we saw that the compiler could inject padding bytes at will inside a structure.
What if we needed to know where those were? We can measure it with offsetof, defined in <stddef.h>.
2
Though some compilers have options to force this to occur—search for __attribute__((packed)) to see how to do this with
GCC.
Chapter 20. structs II: More Fun with structs 151
Let’s modify the code from above to print the offsets of the individual fields in the struct:
1 #include <stdio.h>
2 #include <stddef.h>
3
4 struct foo {
5 int a;
6 char b;
7 int c;
8 char d;
9 };
10
11 int main(void)
12 {
13 printf("%zu\n", offsetof(struct foo, a));
14 printf("%zu\n", offsetof(struct foo, b));
15 printf("%zu\n", offsetof(struct foo, c));
16 printf("%zu\n", offsetof(struct foo, d));
17 }
indicating that we’re using 4 bytes for each of the fields. It’s a little weird, because char is only 1 byte, right?
The compiler is putting 3 padding bytes after each char so that all the fields are 4 bytes long. Presumably
this will run faster on my CPU.
struct child {
struct parent super; // MUST be first
int c, d;
};
Then we are able to pass a pointer to a struct child to a function that expects either that or a pointer to a
struct parent!
Because struct parent super is the first item in the struct child, a pointer to any struct child is
the same as a pointer to that super field3 .
3
super isn’t a keyword, incidentally. I’m just stealing some OOP terminology.
Chapter 20. structs II: More Fun with structs 152
Let’s set up an example here. We’ll make structs as above, but then we’ll pass a pointer to a struct child
to a function that needs a pointer to a struct parent… and it’ll still work.
1 #include <stdio.h>
2
3 struct parent {
4 int a, b;
5 };
6
7 struct child {
8 struct parent super; // MUST be first
9 int c, d;
10 };
11
29 int main(void)
30 {
31 struct child c = {.super.a=1, .super.b=2, .c=3, .d=4};
32
33 print_child(&c);
34 print_parent(&c); // Also works even though it's a struct child!
35 }
See what we did on the last line of main()? We called print_parent() but passed a struct child*
as the argument! Even though print_parent() needs the argument to point to a struct parent, we’re
getting away with it because the first field in the struct child is a struct parent.
Again, this works because a pointer to a struct has the same value as a pointer to the first field in that
struct.
and my assumption that “suitably converted” means “cast to the effective type of the initial member”.
20.8 Bit-Fields
In my experience, these are rarely used, but you might see them out there from time to time, especially in
lower-level applications that pack bits together into larger spaces.
Let’s take a look at some code to demonstrate a use case:
1 #include <stdio.h>
2
3 struct foo {
4 unsigned int a;
5 unsigned int b;
6 unsigned int c;
7 unsigned int d;
8 };
9
10 int main(void)
11 {
12 printf("%zu\n", sizeof(struct foo));
13 }
For me, this prints 16. Which makes sense, since unsigneds are 4 bytes on my system.
But what if we knew that all the values that were going to be stored in a and b could be stored in 5 bits, and
the values in c, and d could be stored in 3 bits? That’s only a total 16 bits. Why have 128 bits reserved for
them if we’re only going to use 16?
Well, we can tell C to pretty-please try to pack these values in. We can specify the maximum number of bits
that values can take (from 1 up the size of the containing type).
We do this by putting a colon after the field name, followed by the field width in bits.
3 struct foo {
4 unsigned int a:5;
5 unsigned int b:5;
6 unsigned int c:3;
7 unsigned int d:3;
8 };
Now when I ask C how big my struct foo is, it tells me 4! It was 16 bytes, but now it’s only 4. It has
“packed” those 4 values down into 4 bytes, which is a four-fold memory savings.
The tradeoff is, of course, that the 5-bit fields can only hold values from 0-31 and the 3-bit fields can only
hold values from 0-7. But life’s all about compromise, after all.
In that example, since a is not adjacent to c, they are both “packed” in their own ints.
So we have one int each for a, b, c, and d. Since my ints are 4 bytes, that’s a grand total of 16 bytes.
A quick rearrangement yields some space savings from 16 bytes down to 12 bytes (on my system):
struct foo { // sizeof(struct foo) == 12 (for me)
unsigned int a:1;
unsigned int c:1;
unsigned int b;
unsigned int d;
};
And now, since a is next to c, the compiler puts them together into a single int.
So we have one int for a combined a and c, and one int each for b and d. For a grand total of 3 ints, or
12 bytes.
Put all your bitfields together to get the compiler to combine them.
And that works—in our code we use a and b, but never dummy. It’s just there to eat up 5 bits to make sure a
and b are in the “required” (by this contrived example) positions within the byte.
C allows us a way to clean this up: unnamed bit-fields. You can just leave the name (dummy) out in this case,
and C is perfectly happy for the same effect:
struct foo {
unsigned char a:2;
unsigned char :5; // <-- unnamed bit-field!
unsigned char b:1;
};
the compiler packs all those into a single unsigned int. But what if you needed a and b in one int, and c
and d in a different one?
There’s a solution for that: put an unnamed bit-field of width 0 where you want the compiler to start anew
with packing bits in a different int:
struct foo {
unsigned int a:1;
unsigned int b:2;
unsigned int :0; // <--Zero-width unnamed bit-field
unsigned int c:3;
unsigned int d:4;
};
It’s analogous to an explicit page break in a word processor. You’re telling the compiler, “Stop packing bits
in this unsigned, and start packing them in the next one.”
By adding the zero-width unnamed bit field in that spot, the compiler puts a and b in one unsigned int,
and c and d in another unsigned int. Two total, for a size of 8 bytes on my system (unsigned ints are
4 bytes each).
20.9 Unions
These are basically just like structs, except the fields overlap in memory. The union will be only large
enough for the largest field, and you can only use one field at a time.
It’s a way to reuse the same memory space for different types of data.
You declare them just like structs, except it’s union. Take a look at this:
union foo {
int a, b, c, d, e, f;
float g, h;
char i, j, k, l;
};
Now, that’s a lot of fields. If this were a struct, my system would tell me it took 36 bytes to hold it all.
But it’s a union, so all those fields overlap in the same stretch of memory. The biggest one is int (or float),
taking up 4 bytes on my system. And, indeed, if I ask for the sizeof the union foo, it tells me 4!
The tradeoff is that you can only portably use one of those fields at a time. However…
Since the members of a union share the same memory, writing to one member necessarily affects the others.
And if you read from one what was written to another, you get some weird effects.
1 #include <stdio.h>
2
3 union foo {
4 float b;
5 short a;
6 };
7
8 int main(void)
9 {
10 union foo x;
11
12 x.b = 3.14159;
13
because under the hood, the object representation for the float 3.14159 was the same as the object represen-
tation for the short 4048. On my system. Your results may vary.
3 union foo {
4 int a, b, c, d, e, f;
5 float g, h;
6 char i, j, k, l;
7 };
8
9 int main(void)
10 {
11 union foo x;
12
16 x.a = 12;
17 printf("%d\n", x.a); // 12
18 printf("%d\n", *foo_int_p); // 12, again
19
Chapter 20. structs II: More Fun with structs 157
20 x.g = 3.141592;
21 printf("%f\n", x.g); // 3.141592
22 printf("%f\n", *foo_float_p); // 3.141592, again
23 }
The reverse is also true. If we have a pointer to a type inside the union, we can cast that to a pointer to the
union and access its members.
union foo x;
int *foo_int_p = (int *)&x; // Pointer to int field
union foo *p = (union foo *)foo_int_p; // Back to pointer to union
All this just lets you know that, under the hood, all these values in a union start at the same place in memory,
and that’s the same as where the entire union is.
char *p;
};
struct b {
int x; //
float y; // Common initial sequence
double *p;
short z;
};
Do you see it? It’s that they start with int followed by float—that’s the common initial sequence. The
members in the sequence of the structs have to be compatible types. And we see that with x and y, which
are int and float respectively.
Now let’s build a union of these:
union foo {
struct a sa;
struct b sb;
};
What this rule tells us is that we’re guaranteed that the members of the common initial sequences are inter-
changeable in code. That is:
• f.sa.x is the same as f.sb.x.
and
Chapter 20. structs II: More Fun with structs 158
That is, if we weren’t allowed this and we passed the union to some function, how would that function know
which member of the union was the one it should look at?
Take a look at these structs. Note the common initial sequence:
1 #include <stdio.h>
2
3 struct common {
4 int type; // common initial sequence
5 };
6
7 struct antelope {
8 int type; // common initial sequence
9
10 int loudness;
11 };
12
13 struct octopus {
14 int type; // common initial sequence
15
16 int sea_creature;
17 float intelligence;
18 };
So far, nothing special has happened here. It seems like the type field is completely useless.
But now let’s make a generic function that prints a union animal. It has to somehow be able to tell if it’s
looking at a struct antelope or a struct octopus.
Because of the magic of common initial sequences, it can look up the animal type in any of these places for
a particular union animal x:
int type = x.common.type; \\ or...
int type = x.antelope.type; \\ or...
int type = x.octopus.type;
And, as you might have guessed, the struct common is there so code can agnostically look at the type
without mentioning a particular animal.
Let’s look at the code to print a union animal:
29 void print_animal(union animal *x)
30 {
31 switch (x->common.type) {
32 case ANTELOPE:
33 printf("Antelope: loudness=%d\n", x->antelope.loudness);
34 break;
35
36 case OCTOPUS:
37 printf("Octopus : sea_creature=%d\n", x->octopus.sea_creature);
38 printf(" intelligence=%f\n", x->octopus.intelligence);
39 break;
40
41 default:
42 printf("Unknown animal type\n");
43 }
44
45 }
46
47 int main(void)
48 {
49 union animal a = {.antelope.type=ANTELOPE, .antelope.loudness=12};
50 union animal b = {.octopus.type=OCTOPUS, .octopus.sea_creature=1,
51 .octopus.intelligence=12.8};
52
53 print_animal(&a);
54 print_animal(&b);
55 }
See how on line 29 we’re just passing in the union—we have no idea what type of animal struct is in use
within it.
But that’s OK! Because on line 31 we check the type to see if it’s an antelope or an octopus. And then we
can look at the proper struct to get the members.
It’s definitely possible to get this same effect using just structs, but you can do it this way if you want the
memory-saving effects of a union.
That defines a variable s that is of anonymous struct type (because the struct has no name tag), with
members x and y.
So things like this are valid:
s.x = 34;
s.y = 90;
Chapter 20. structs II: More Fun with structs 160
Turns out you can drop those unnamed structs in unions just like you might expect:
union foo {
struct { // unnamed!
int x, y;
} a;
struct { // unnamed!
int z, w;
} b;
};
f.a.x = 1;
f.a.y = 2;
f.b.z = 3;
f.b.w = 4;
No problem!
3 struct foo {
4 int x, y;
5 };
6
12 int main(void)
13 {
14 struct foo a = f(); // Copy is made
15
Fun fact: if you do this, you can use the . operator right off the function call:
16 printf("%d %d\n", f().x, f().y);
And the same holds true for returning pointers to structs and unions—just be sure to use the -> arrow
operator in that case.
Chapter 21
We’ve talked about how char types are actually just small integer types… but it’s the same for a character
in single quotes.
But a string in double quotes is type const char *.
Turns out there are few more types of strings and characters, and it leads down one of the most infamous
rabbit holes in the language: the whole multibyte/wide/Unicode/localization thingy.
We’re going to peer into that rabbit hole, but not go in. …Yet!
But what if we want some special characters in there that we can’t type on the keyboard because they don’t
exist (e.g. “€”), or even if we want a character that’s a single quote? We clearly can’t do this:
char t = ''';
To do these things, we use something called escape sequences. These are the backslash character (\) followed
by another character. The two (or more) characters together have special meaning.
For our single quote character example, we can put an escape (that is, \) in front of the central single quote
to solve it:
char t = '\'';
Now C knows that \' means just a regular quote we want to print, not the end of the character sequence.
You can say either “backslash” or “escape” in this context (“escape that quote”) and C devs will know what
you’re talking about. Also, “escape” in this context is different than your Esc key or the ASCII ESC code.
1
I just made up that number, but it’s probably not far off
162
Chapter 21. Characters and Strings II 163
Code Description
\n Newline character—when printing, continue subsequent output on the next line
\' Single quote—used for a single quote character constant
\" Double quote—used for a double quote in a string literal
\\ Backslash—used for a literal \ in a string or character
Here are some examples of the escapes and what they output when printed.
printf("Use \\n for newline\n"); // Use \n for newline
printf("Say \"hello\"!\n"); // Say "hello"!
printf("%c\n", '\''); // '
Code Description
\a Alert. This makes the terminal make a sound or flash, or both!
\b Backspace. Moves the cursor back a character. Doesn’t delete the character.
\f Formfeed. This moves to the next “page”, but that doesn’t have much modern meaning.
On my system, this behaves like \v.
\r Return. Move to the beginning of the same line.
\t Horizontal tab. Moves to the next horizontal tab stop. On my machine, this lines up on
columns that are multiples of 8, but YMMV.
\v Vertical tab. Moves to the next vertical tab stop. On my machine, this moves to the same
column on the next line.
\? Literal question mark. Sometimes you need this to avoid trigraphs, as shown below.
4 int main(void)
5 {
6 for (int i = 10; i >= 0; i--) {
7 printf("\rT minus %d second%s... \b", i, i != 1? "s": "");
8
15 printf("\rLiftoff! \n");
16 }
Chapter 21. Characters and Strings II 164
Quite a few things are happening on line 7. First of all, we lead with a \r to get us to the beginning of the
current line, then we overwrite whatever’s there with the current countdown. (There’s ternary operator out
there to make sure we print 1 second instead of 1 seconds.)
Also, there’s a space after the ... That’s so that we properly overwrite the last . when i drops from 10 to
9 and we get a column narrower. Try it without the space to see what I mean.
And we wrap it up with a \b to back up over that space so the cursor sits at the exact end of the line in an
aesthetically-pleasing way.
Note that line 14 also has a lot of spaces at the end to overwrite the characters that were already there from
the countdown.
Finally, we have a weird fflush(stdout) in there, whatever that means. Short answer is that most termi-
nals are line buffered by default, meaning they don’t actually display anything until a newline character is
encountered. Since we don’t have a newline (we just have \r), without this line, the program would just
sit there until Liftoff! and then print everything all in one instant. fflush() overrides this behavior and
forces output to happen right now.
Code Description
\123 Embed the byte with octal value 123, 3 digits exactly.
\x4D Embed the byte with hex value 4D, 2 digits.
\u2620 Embed the Unicode character at code point with hex value 2620, 4 digits.
\U0001243F Embed the Unicode character at code point with hex value 1243F, 8 digits.
Here’s an example of the less-commonly used octal notation to represent the letter B in between A and C.
Normally this would be used for some kind of special unprintable character, but we have other ways to do
that, below, and this is just an octal demo:
printf("A\102C\n"); // 102 is `B` in ASCII/UTF-8
Note there’s no leading zero on the octal number when you include it this way. But it does need to be three
characters, so pad with leading zeros if you need to.
But far more common is to use hex constants these days. Here’s a demo that you shouldn’t use, but it demos
embedding the UTF-8 bytes 0xE2, 0x80, and 0xA2 in a string, which corresponds to the Unicode “bullet”
character (•).
printf("\xE2\x80\xA2 Bullet 1\n");
printf("\xE2\x80\xA2 Bullet 2\n");
printf("\xE2\x80\xA2 Bullet 3\n");
Produces the following output if you’re on a UTF-8 console (or probably garbage if you’re not):
• Bullet 1
• Bullet 2
• Bullet 3
But that’s a crummy way to do Unicode. You can use the escapes \u (16-bit) or \U (32-bit) to just refer to
Unicode by code point number. The bullet is 2022 (hex) in Unicode, so you can do this and get more portable
results:
printf("\u2022 Bullet 1\n");
printf("\u2022 Bullet 2\n");
printf("\u2022 Bullet 3\n");
Be sure to pad \u with enough leading zeros to get to four characters, and \U with enough to get to eight.
For example, that bullet could be done with \U and four leading zeros:
printf("\U00002022 Bullet 1\n");
166
Chapter 22. Enumerated Types: enum 167
if values are omitted, numbering continues counting in the positive direction from whichever value was last
specified. For example:
enum {
A, // 0, default starting value
B, // 1
C=4, // 4, manually set
D, // 5
E, // 6
F=3 // 3, manually set
G, // 4
H // 5
}
It’s gotten more popular in languages of the recent decades so you might be pleased to see it.
22.1.3 Scope
enums scope as you’d expect. If at file scope, the whole file can see it. If in a block, it’s local to that block.
It’s really common for enums to be defined in header files so they can be #included at file scope.
22.1.4 Style
As you’ve noticed, it’s common to declare the enum symbols in uppercase (with underscores).
This isn’t a requirement, but is a very, very common idiom.
You can give them a tag name so you can refer to the type later and declare variables of that type.
Now, since enums are integer types, why not just use int?
In C, the best reason for this is code clarity–it’s a nice, typed way to describe your thinking in code. C (unlike
C++) doesn’t actually enforce any values being in range for a particular enum.
Let’s do an example where we declare a variable r of type enum resource that can hold those values:
// Named enum, type is "enum resource"
enum resource {
SHEEP,
WHEAT,
WOOD,
BRICK,
ORE
};
if (r == BRICK) {
printf("I'll trade you a brick for two sheep.\n");
}
You can also typedef these, of course, though I personally don’t like to.
typedef enum {
SHEEP,
WHEAT,
WOOD,
BRICK,
ORE
} RESOURCE;
RESOURCE r = BRICK;
Another shortcut that’s legal but rare is to declare variables when you declare the enum:
// Declare an enum and some initialized variables of that type:
enum {
SHEEP,
WHEAT,
WOOD,
BRICK,
ORE
} r = BRICK, s = WOOD;
You can also give the enum a name so you can use it later, which is probably what you want to do in most
cases:
// Declare an enum and some initialized variables of that type:
WHEAT,
WOOD,
BRICK,
ORE
} r = BRICK, s = WOOD;
In short, enums are a great way to write nice, scoped, typed, clean code.
Chapter 23
Here’s where we cover some intermediate and advanced pointer usage. If you don’t have pointers down well,
review the previous chapters on pointers and pointer arithmetic before starting on this stuff.
3 int main(void)
4 {
5 int x = 3490; // Type: int
6 int *p = &x; // Type: pointer to an int
7
1
There’s some devil in the details with values that are stored in registers only, but we can safely ignore that for our purposes here.
Also the C spec makes no stance on these “register” things beyond the register keyword, the description for which doesn’t mention
registers.
170
Chapter 23. Pointers III: Pointers to Pointers and More 171
Straightforward enough, right? We have two types represented: int and int*, and we set up p to point to x.
Then we can dereference p on line 8 and print out the value 3490.
But, like we said, we can have a pointer to any variable… so does that mean we can have a pointer to p?
In other words, what type is this expression?
int x = 3490; // Type: int
int *p = &x; // Type: pointer to an int
If x is an int, then &x is a pointer to an int that we’ve stored in p which is type int*. Follow? (Repeat this
paragraph until you do!)
And therefore &p is a pointer to an int*, AKA a “pointer to a pointer to an int”. AKA “int-pointer-pointer”.
Got it? (Repeat the previous paragraph until you do!)
We write this type with two asterisks: int **. Let’s see it in action.
1 #include <stdio.h>
2
3 int main(void)
4 {
5 int x = 3490; // Type: int
6 int *p = &x; // Type: pointer to an int
7 int **q = &p; // Type: pointer to pointer to int
8
Let’s make up some pretend addresses for the above values as examples and see what these three variables
might look like in memory. The address values, below are just made up by me for example purposes:
Indeed, let’s try it for real on my computer2 and print out the pointer values with %p and I’ll do the same table
again with actual references (printed in hex).
You can see those addresses are the same except the last byte, so just focus on those.
On my system, ints are 4 bytes, which is why we’re seeing the address go up by 4 from x to p3 and then
goes up by 8 from p to q. On my system, all pointers are 8 bytes.
2
You’re very likely to get different numbers on yours.
3
There is absolutely nothing in the spec that says this will always work this way, but it happens to work this way on my system.
Chapter 23. Pointers III: Pointers to Pointers and More 172
Does it matter if it’s an int* or an int**? Is one more bytes than the other? Nope! Remember that all
pointers are addresses, that is indexes into memory. And on my machine you can represent an index with 8
bytes… doesn’t matter what’s stored at that index.
Now check out what we did there on line 9 of the previous example: we double dereferenced q to get back
to our 3490.
This is the important bit about pointers and pointers to pointers:
• You can get a pointer to anything with & (including to a pointer!)
• You can get the thing a pointer points to with * (including a pointer!)
So you can think of & as being used to make pointers, and * being the inverse—it goes the opposite direction
of &—to get to the thing pointed to.
In terms of type, each time you &, that adds another pointer level to the type.
And each time you use dereference (*), it does the opposite:
Note that you can use multiple *s in a row to quickly dereference, just like we saw in the example code with
**q, above. Each one strips away one level of indirection.
4
Even if E is NULL, it turns out, weirdly.
Chapter 23. Pointers III: Pointers to Pointers and More 173
means that you can’t modify p. Trying to p++ would give you a compile-time error.
But how does that work with int ** or int ***? Where does the const go, and what does it mean?
Let’s start with the simple bit. The const right next to the variable name refers to that variable. So if you
want an int*** that you can’t change, you can do this:
int ***const p;
And that works, too. Now we can’t modify q, or the pointer q points to.
Chapter 23. Pointers III: Pointers to Pointers and More 174
return dest;
}
(There are some good examples of post-increment and post-decrement in there for you to study, as well.)
It’s important to note that the version, above, is probably less efficient than the one that comes with your
system.
But you can pass pointers to anything into it, and it’ll copy those objects. Could be int*, struct animal*,
or anything.
Let’s do another example that prints out the object representation bytes of a struct so we can see if there’s
any padding in there and what values it has6 .
1 #include <stdio.h>
2
3 struct foo {
4 char a;
5 int b;
6 };
7
8 int main(void)
9 {
10 struct foo x = {0x12, 0x12345678};
11 unsigned char *p = (unsigned char *)&x;
12
5
https://beej.us/guide/bgclr/html/split/stringref.html#man-memcpy
6
Your C compiler is not required to add padding bytes, and the values of any padding bytes that are added are indeterminate.
Chapter 23. Pointers III: Pointers to Pointers and More 175
15 }
16 }
What we have there is a struct foo that’s built in such a way that should encourage a compiler to inject
padding bytes (though it doesn’t have to). And then we get an unsigned char * to the first byte of the
struct foo variable x.
From there, all we need to know is the sizeof x and we can loop through that many bytes, printing out the
values (in hex for ease).
Running this gives a bunch of numbers as output. I’ve annotated it below to identify where the values were
stored:
12 | x.a == 0x12
AB |
BF | padding bytes with "random" value
26 |
78 |
56 | x.b == 0x12345678
34 |
12 |
On all systems, sizeof(char) is 1, and we see that first byte at the top of the output holding the value 0x12
that we stored there.
Then we have some padding bytes—for me, these varied from run to run.
Finally, on my system, sizeof(int) is 4, and we can see those 4 bytes at the end. Notice how they’re the
same bytes as are in the hex value 0x12345678, but strangely in reverse order7 .
So that’s a little peek under the hood at the bytes of a more complex entity in memory.
Personally, I always use NULL when I mean NULL, but you might see some other variants from time to time.
Though '\0' (a byte with all bits set to zero) will also compare equal, it’s weird to compare it to a pointer; you
should compare NULL against the pointer. (Of course, lots of times in string processing, you’re comparing
the thing the pointer points to to '\0', and that’s right.)
0 is called the null pointer constant, and, when compared to or assigned into another pointer, it is converted
to a null pointer of the same type.
are implementation-defined, so they aren’t portable. And weird things could happen.
C does make one guarantee, though: you can convert a pointer to a uintptr_t type and you’ll be able to
convert it back to a pointer without losing any data.
uintptr_t is defined in <stdint.h>8 .
Additionally, if you feel like being signed, you can use intptr_t to the same effect.
But the following isn’t good because int and float are not compatible types:
int a = 1;
float *p = (float *)&a;
Here’s a demo program that does some aliasing. It takes a variable v of type int32_t and aliases it to a
pointer to a struct words. That struct has two int16_ts in it. These types are incompatible, so we’re
in violation of strict aliasing rules. The compiler will assume that these two pointers never point to the same
object… but we’re making it so they do. Which is naughty of us.
Let’s see if we can break something.
1 #include <stdio.h>
2 #include <stdint.h>
3
4 struct words {
5 int16_t v[2];
6 };
7
8
It’s an optional feature, so it might not be there—but it probably is.
Chapter 23. Pointers III: Pointers to Pointers and More 177
9 {
10 for (int i = 0; i < 5; i++) {
11 (*pv)++;
12
19 int main(void)
20 {
21 int32_t v = 0x12345678;
22
25 fun(&v, pw);
26 }
See how I pass in the two incompatible pointers to fun()? One of the types is int32_t* and the other is
struct words*.
But they both point to the same object: the 32-bit value initialized to 0x12345678.
So if we look at the fields in the struct words, we should see the two 16-bit halves of that number. Right?
And in the fun() loop, we increment the pointer to the int32_t. That’s it. But since the struct points to
that same memory, it, too, should be updated to the same value.
So let’s run it and get this, with the 32-bit value on the left and the two 16-bit portions on the right. It should
match9 :
12345679, 1234-5679
1234567a, 1234-567a
1234567b, 1234-567b
1234567c, 1234-567c
1234567d, 1234-567d
They’re off by one! But they point to the same memory! How could this be? Answer: it’s undefined behavior
to alias memory like that. Anything is possible, except not in a good way.
If your code violates strict aliasing rules, whether it works or not depends on how someone decides to compile
it. And that’s a bummer since that’s beyond your control. Unless you’re some kind of omnipotent deity.
Unlikely, sorry.
GCC can be forced to not use the strict aliasing rules with -fno-strict-aliasing. Compiling the demo
program, above, with -O3 and this flag causes the output to be as expected.
9
I’m printing out the 16-bit values reversed since I’m on a little-endian machine and it makes it easier to read here.
Chapter 23. Pointers III: Pointers to Pointers and More 178
Lastly, type punning is using pointers of different types to look at the same data. Before strict aliasing, this
kind of things was fairly common:
int a = 0x12345678;
short b = *((short *)&a); // Violates strict aliasing
If you want to do type punning (relatively) safely, see the section on Unions and Type Punning.
ptrdiff_t d = g - f; // difference is 40
And you can print it by prefixing the integer format specifier with t:
printf("%td\n", d); // Print decimal: 40
printf("%tX\n", d); // Print hex: 28
Also notice that you don’t have to give the parameters names. But you can if you want; they’re just ignored.
10
Assuming they point to the same array object.
Chapter 23. Pointers III: Pointers to Pointers and More 179
So now that we know how to declare a variable, how do we know what to assign into it? How do we get the
address of a function?
Turns out there’s a shortcut just like with getting a pointer to an array: you can just refer to the bare function
name without parens. (You can put an & in front of this if you like, but it’s unnecessary and not idiomatic.)
Once you have a pointer to a function, you can call it just by adding parens and an argument list.
Let’s do a simple example where I effectively make an alias for a function by setting a pointer to it. Then
we’ll call it.
This code prints out 3490:
1 #include <stdio.h>
2
3 void print_int(int n)
4 {
5 printf("%d\n", n);
6 }
7
8 int main(void)
9 {
10 // Assign p to point to print_int:
11
Notice how the type of p represents the return value and parameter types of print_int. It has to, or else C
will complain about incompatible pointer types.
One more example here shows how we might pass a pointer to a function as an argument to another function.
We’ll write a function that takes a couple integer arguments, plus a pointer to a function that operates on
those two arguments. Then it prints the result.
1 #include <stdio.h>
2
17 printf("%d\n", result);
18 }
19
20 int main(void)
21 {
22 print_math(add, 5, 7); // 12
23 print_math(mult, 5, 7); // 35
24 }
Take a moment to digest that. The idea here is that we’re going to pass a pointer to a function to
print_math(), and it’s going to call that function to do some math.
This way we can change the behavior of print_math() by passing another function into it. You can see we
do that on lines 22-23 when we pass in pointers to functions add and mult, respectively.
Now, on line 13, I think we can all agree the function signature of print_math() is a sight to behold. And,
if you can believe it, this one is actually pretty straight-forward compared to some things you can construct11 .
But let’s digest it. Turns out there are only three parameters, but they’re a little hard to see:
// op x y
// |-----------------| |---| |---|
void print_math(int (*op)(int, int), int x, int y)
The first, op, is a pointer to a function that takes two ints as arguments and returns an int. This matches
the signatures for both add() and mult().
The second and third, x and y, are just standard int parameters.
Slowly and deliberately let your eyes play over the signature while you identify the working parts. One thing
that always stands out for me is the sequence (*op)(, the parens and the asterisk. That’s the giveaway it’s a
pointer to a function.
Finally, jump back to the Pointers II chapter for a pointer-to-function example using the built-in qsort().
11
The Go Programming Language drew its type declaration syntax inspiration from the opposite of what C does.
Chapter 24
Bitwise Operations
These numeric operations effectively allow you to manipulate individual bits in variables, fitting since C is
such a low-level langauge1 .
If you’re not familiar with bitwise operations, Wikipedia has a good bitwise article2 .
Note how they’re similar to the Boolean operators && and ||.
These have assignment shorthand variants similar to += and -=:
181
Chapter 24. Bitwise Operations 182
Watch for undefined behavior: no negative shifts, and no shifts that are larger than the size of the promoted
left operand.
Also watch for implementation-defined behavior: if you right-shift a negative number, the results are
implementation-defined. (It’s perfectly fine to right-shift a signed int, just make sure it’s positive.)
Chapter 25
Variadic Functions
Variadic is a fancy word for functions that take arbitrary numbers of arguments.
A regular function takes a specific number of arguments, for example:
int add(int x, int y)
{
return x + y;
}
You can only call that with exactly two arguments which correspond to parameters x and y.
add(2, 3);
add(5, 12);
But if you try it with more, the compiler won’t let you:
add(2, 3, 4); // ERROR
add(5); // ERROR
This leads us to one of the limitations of variadic functions in C: they must have at least one argument.
But aside from that, they’re pretty flexible, even allows arguments to have different types just like printf()
does.
Let’s see how they work!
183
Chapter 25. Variadic Functions 184
What you do is put all the arguments that must be passed first (and remember there has to be at least one) and
after that, you put .... Just like this:
void func(int a, ...) // Literally 3 dots here
int main(void)
{
func(2, 3, 4, 5, 6);
}
So, great, we can get that first argument that’s in variable a, but what about the rest of the arguments? How
do you get to them?
Here’s where the fun begins!
14 total += n;
15 }
16
Chapter 25. Variadic Functions 185
19 return total;
20 }
21
22 int main(void)
23 {
24 printf("%d\n", add(4, 6, 2, -4, 17)); // 6 + 2 - 4 + 17 = 21
25 printf("%d\n", add(2, 22, 44)); // 22 + 44 = 66
26 }
(Note that when printf() is called, it uses the number of %ds (or whatever) in the format string to know
how many more arguments there are!)
If the syntax of va_arg() is looking strange to you (because of that loose type name floating around in there),
you’re not alone. These are implemented with preprocessor macros in order to get all the proper magic in
there.
va_copy() can be useful if you need to scan ahead through the arguments but need to also remember your
current place.
8 // Do my custom work
9 printf("The serial number is: %d\n", serial);
10
16 return rv;
17 }
18
19 int main(void)
20 {
21 int x = 10;
22 float y = 3.2;
23
See what we did there? On lines 12-14 we started a new va_list variable, and then just passed it right into
vprintf(). And it knows just want to do with it, because it has all the printf() smarts built-in.
We still have to call va_end() when we’re done, though, so don’t forget that!
Chapter 26
Localization is the process of making your app ready to work well in different locales (or countries).
As you might know, not everyone uses the same character for decimal points or for thousands separators…
or for currency.
These locales have names, and you can select one to use. For example, a US locale might write a number
like:
100,000.00
Whereas in Brazil, the same might be written with the commas and decimal points swapped:
100.000,00
Makes it easier to write your code so it ports to other nationalities with ease!
Well, sort of. Turns out C only has one built-in locale, and it’s limited. The spec really leaves a lot of
ambiguity here; it’s hard to be completely portable.
But we’ll do our best!
You’ll want to call that so that the program gets initialized with your current locale.
Getting into more details, there is one more thing you can do and stay portable:
setlocale(LC_ALL, "C"); // Use the default C locale
but that’s called by default every time your program starts, so there’s not much need to do it yourself.
In that second string, you can specify any locale supported by your system. This is completely system-
dependent, so it will vary. On my system, I can specify this:
setlocale(LC_ALL, "en_US.UTF-8"); // Non-portable!
And that’ll work. But it’s only portable to systems which have that exact same name for that exact same
locale, and you can’t guarantee it.
187
Chapter 26. Locale and Internationalization 188
By passing in an empty string ("") for the second argument, you’re telling C, “Hey, figure out what the
current locale on this system is so I don’t have to tell you.”
This function returns a pointer to a statically-allocated struct lconv that has all that juicy information
you’re looking for.
Here are the fields of struct lconv and their meanings.
First, some conventions. An _p_ means “positive”, and _n_ means “negative”, and int_ means “interna-
tional”. Though a lot of these are type char or char*, most (or the strings they point to) are actually treated
as integers2 .
Before we go further, know that CHAR_MAX (from <limits.h>) is the maximum value that can be held in a
char. And that many of the following char values use that to indicate the value isn’t available in the given
locale.
Field Description
char *mon_decimal_point Decimal pointer character for money, e.g. ".".
char *mon_thousands_sep Thousands separator character for money, e.g. ",".
char *mon_grouping Grouping description for money (see below).
char *positive_sign Positive sign for money, e.g. "+" or "".
char *negative_sign Negative sign for money, e.g. "-".
char *currency_symbol Currency symbol, e.g. "$".
char frac_digits When printing monetary amounts, how many digits to print past the
decimal point, e.g. 2.
char p_cs_precedes 1 if the currency_symbol comes before the value for a non-negative
monetary amount, 0 if after.
char n_cs_precedes 1 if the currency_symbol comes before the value for a negative
monetary amount, 0 if after.
char p_sep_by_space Determines the separation of the currency symbol from the value for
non-negative amounts (see below).
char n_sep_by_space Determines the separation of the currency symbol from the value for
negative amounts (see below).
char p_sign_posn Determines the positive_sign position for non-negative values.
char p_sign_posn Determines the positive_sign position for negative values.
char *int_curr_symbol International currency symbol, e.g. "USD ".
char int_frac_digits International value for frac_digits.
char int_p_cs_precedes International value for p_cs_precedes.
char int_n_cs_precedes International value for n_cs_precedes.
char int_p_sep_by_space International value for p_sep_by_space.
1
“This planet has—or rather had—a problem, which was this: most of the people living on it were unhappy for pretty much of the
time. Many solutions were suggested for this problem, but most of these were largely concerned with the movement of small green
pieces of paper, which was odd because on the whole it wasn’t the small green pieces of paper that were unhappy.” —The Hitchhiker’s
Guide to the Galaxy, Douglas Adams
2
Remember that char is just a byte-sized integer.
Chapter 26. Locale and Internationalization 189
Field Description
char int_n_sep_by_space International value for n_sep_by_space.
char int_p_sign_posn International value for p_sign_posn.
char int_n_sign_posn International value for n_sign_posn.
These are groups of three. Group 0 (just left of the decimal) has 3 digits. Group 1 (next group to the left) has
3 digits, and the last one also has 3.
So we could describe these groups, from the right (the decimal) to the left with a bunch of integer values
representing the group sizes:
3 3 3
but that’s crazy. Luckily, we can specify 0 to indicate that the previous group size repeats:
3 0
Which means to repeat every 3. That would handle $100, $1,000, $10,000, $10,000,000, $100,000,000,000,
and so on.
You can go legitimately crazy with these to indicate some weird groupings.
For example:
4 3 2 1 0
would indicate:
$1,0,0,0,0,00,000,0000.00
One more value that can occur is CHAR_MAX. This indicates that no more grouping should occur, and can
appear anywhere in the array, including the first value.
3 2 CHAR_MAX
would indicate:
100000000,00,000.00
for example.
And simply having CHAR_MAX in the first array position would tell you there was to be no grouping at all.
Chapter 26. Locale and Internationalization 190
Value Description
0 No space between currency symbol and value.
1 Separate the currency symbol (and sign, if any) from the value with a space.
2 Separate the sign symbol from the currency symbol (if adjacent) with a space,
otherwise separate the sign symbol from the value with a space.
Value Description
0 Put parens around the value and the currency symbol.
1 Put the sign string in front of the currency symbol and value.
2 Put the sign string after the currency symbol and value.
3 Put the sign string directly in front of the currency symbol.
4 Put the sign string directly behind the currency symbol.
Macro Description
LC_ALL Set all of the following to the given locale.
LC_COLLATE Controls the behavior of the strcoll() and strxfrm() functions.
LC_CTYPE Controls the behavior of the character-handling functions3 .
LC_MONETARY Controls the values returned by localeconv().
LC_NUMERIC Controls the decimal point for the printf() family of functions.
LC_TIME Controls time formatting of the strftime() and wcsftime() time and date
printing functions.
It’s pretty common to see LC_ALL being set, but, hey, at least you have options.
Also I should point out that LC_CTYPE is one of the biggies because it ties into wide characters, a significant
can of worms that we’ll talk about later.
3
Except for isdigit() and isxdigit().
Chapter 27
Before we begin, note that this is an active area of language development in C as it works to get past some,
erm, growing pains. When C2x comes out, updates here are probable.
Most people are basically interested in the deceptively simple question, “How do I use such-and-such char-
acter set in C?” We’ll get to that. But as we’ll see, it might already work on your system. Or you might have
to punt to a third-party library.
We’re going to talk about a lot of things this chapter—some are platform agnostic, and some are C-specific.
Let’s get an outline first of what we’re going to look at:
• Unicode background
• Character encoding background
• Source and Execution character Sets
• Using Unicode and UTF-8
• Using other character types like wchar_t, char16_t, and char32_t
Let’s dive in!
192
Chapter 27. Unicode, Wide Characters, and All That 193
“π”.)
Each code point represents a unique character. And each character has a unique numeric code point associated
with it.
For example, in Unicode, the numeric value 66 represents “B”, and 960 represents “π”. Other character map-
pings that aren’t Unicode use different values, potentially, but let’s forget them and concentrate on Unicode,
the future!
So that’s one thing: there’s a number that represents each character. In Unicode, these numbers run from 0
to over 1 million.
Got it?
Because we’re about to flip the table a little.
27.3 Encoding
If you recall, an 8-bit byte can hold values from 0-255, inclusive. That’s great for “B” which is 66—that fits
in a byte. But “π” is 960, and that doesn’t fit in a byte! We need another byte. How do we store all that in
memory? Or what about bigger numbers, like 195,024? That’s going to need a number of bytes to hold.
The Big Question: how are these numbers represented in memory? This is what we call the encoding of the
characters.
So we have two things: one is the code point which tells us effectively the serial number of a particular
character. And we have the encoding which tells us how we’re going to represent that number in memory.
There are plenty of encodings. You can make up your own right now, if you want1 . But we’re going to look
at some really common encodings that are in use with Unicode.
Encoding Description
UTF-8 A byte-oriented encoding that uses a variable number of bytes per character.
This is the one to use.
UTF-16 A 16-bit per character2 encoding.
UTF-32 A 32-bit per character encoding.
With UTF-16 and UTF-32, the byte order matters, so you might see UTF-16BE for big-endian and UTF-
16LE for little-endian. Same for UTF-32. Technically, if unspecified, you should assume big-endian. But
since Windows uses UTF-16 extensively and is little-endian, sometimes that is assumed3 .
Let’s look at some examples. I’m going to write the values in hex because that’s exactly two digits per 8-bit
byte, and it makes it easier to see how things are arranged in memory.
1
For example, we could store the code point in a big-endian 32-bit integer. Straightforward! We just invented an encoding! Actually
not; that’s what UTF-32BE encoding is. Oh well—back to the grind!
2
Ish. Technically, it’s variable width—there’s a way to represent code points higher than 216 by putting two UTF-16 characters
together.
3
There’s a special character called the Byte Order Mark (BOM), code point 0xFEFF, that can optionally precede the data stream and
indicate the endianess. It is not required, however.
Chapter 27. Unicode, Wide Characters, and All That 194
Look in there for the patterns. Note that UTF-16BE and UTF-32BE are simply the code point represented
directly as 16- and 32-bit values4 .
Little-endian is the same, except the bytes are in little-endian order.
Then we have UTF-8 at the end. First you might notice that the single-byte code points are represented as
a single byte. That’s nice. You might also notice that different code points take different number of bytes.
This is a variable-width encoding.
So as soon as we get above a certain value, UTF-8 starts using additional bytes to store the values. And they
don’t appear to correlate with the code point value, either.
The details of UTF-8 encoding5 are beyond the scope of this guide, but it’s enough to know that it has a
variable number of bytes per code point, and those byte values don’t match up with the code point except for
the first 128 code points. If you really want to learn more, Computerphile has a great UTF-8 video with Tom
Scott6 .
That last bit is a neat thing about Unicode and UTF-8 from a North American perspective: it’s backward
compatible with 7-bit ASCII encoding! So if you’re used to ASCII, UTF-8 is the same! Every ASCII-
encoded document is also UTF-8 encoded! (But not the other way around, obviously.)
It’s probably that last point more than any other that is driving UTF-8 to take over the world.
Those are the characters you can use in your source and remain 100% portable.
The execution character set will additionally have characters for alert (bell/flash), backspace, carriage return,
and newline.
4
Again, this is only true in UTF-16 for characters that fit in two bytes.
5
https://en.wikipedia.org/wiki/UTF-8
6
https://www.youtube.com/watch?v=MijmeoH9LT4
Chapter 27. Unicode, Wide Characters, and All That 195
But most people don’t go to that extreme and freely use their extended character sets in source and executable,
especially now that Unicode and UTF-8 are getting more common. I mean, the basic character set doesn’t
even allow for @, $, or `!
Notably, it’s a pain (though possible with escape sequences) to enter Unicode characters using only the basic
character set.
27.5 Unicode in C
Before I get into encoding in C, let’s talk about Unicode from a code point standpoint. There is a way in C
to specify Unicode characters and these will get translated by the compiler into the execution character set7 .
So how do we do it?
How about the euro symbol, code point 0x20AC. (I’ve written it in hex because both ways of representing it
in C require hex.) How can we put that in our C code?
Use the \u escape to put it in a string, e.g. "\u20AC" (case for the hex doesn’t matter). You must put exactly
four hex digits after the \u, padding with leading zeros if necessary.
Here’s an example:
char *s = "\u20AC1.23";
So \u works for 16-bit Unicode code points, but what about ones bigger than 16 bits? For that, we need
capitals: \U.
For example:
char *s = "\U0001D4D1";
It’s the same as \u, just with 32 bits instead of 16. These are equivalent:
\u03C0
\U000003C0
Again, these are translated into the execution character set during compilation. They represent Unicode code
points, not any specific encoding. Furthermore, if a Unicode code point is not representable in the execution
character set, the compiler can do whatever it wants with it.
Now, you might wonder why you can’t just do this:
char *s = "€1.23";
And you probably can, given a modern compiler. The source character set will be translated for you into the
execution character set by the compiler. But compilers are free to puke out if they find any characters that
aren’t included in their extended character set, and the € symbol certainly isn’t in the basic character set.
Caveat from the spec: you can’t use \u or \U to encode any code points below 0xA0 except for 0x24 ($),
0x40 (@), and 0x60 (`)—yes, those are precisely the trio of common punctuation marks missing from the
basic character set. Apparently this restriction is relaxed in the upcoming version of the spec.
7
Presumably the compiler makes the best effort to translate the code point to whatever the output encoding is, but I can’t find any
guarantees in the spec.
Chapter 27. Unicode, Wide Characters, and All That 196
Finally, you can also use these in identifiers in your code, with some restrictions. But I don’t want to get into
that here. We’re all about string handling in this chapter.
And that’s about it for Unicode in C (except encoding).
What we’re saying here is that a particular character that’s not in the basic character set could be composed
of multiple bytes. Up to MB_LEN_MAX of them (from <limits.h>). Sure, it only looks like one character on
the screen, but it could be multiple bytes.
You can throw Unicode values in there, as well, as we saw earlier:
char *s = "\u20AC1.23";
But here we’re getting into some weirdness, because check this out:
char *s = "\u20AC1.23"; // €1.23
printf("%zu\n", strlen(s)); // 7!
The string length of "€1.23" is 7?! Yes! Well, on my system, yes! Remember that strlen() returns the
number of bytes in the string, not the number of characters. (When we get to “wide characters”, coming up,
we’ll see a way to get the number of characters in the string.)
Note that while C allows individual multibyte char constants (as opposed to char*), the behavior of these
varies by implementation and your compiler might warn on it.
GCC, for example, warns of multi-character character constants for the following two lines (and, on my
system, prints out the UTF-8 encoding):
printf("%x\n", '€');
printf("%x\n", '\u20ac');
But wait, you’re saying—if it’s only 16 bits, it’s not big enough to hold all the Unicode code points, is it?
You’re right—it’s not. The spec doesn’t require it to be. It just has to be able to represent all the characters
in the current locale.
This can cause grief with Unicode on platforms with 16-bit wchar_ts (ahem—Windows). But that’s out of
scope for this guide.
You can declare a string or character of this type with the L prefix, and you can print them with the %ls (“ell
ess”) format specifier. Or print an individual wchar_t with %lc.
wchar_t *s = L"Hello, world!";
wchar_t c = L'B';
Now—are those characters stored as Unicode code points, or not? Depends on the implementation. But you
can test if they are with the macro __STDC_ISO_10646__. If this is defined, the answer is, “It’s Unicode!”
More detailedly, the value in that macro is an integer in the form yyyymm that lets you know what Unicode
standard you can rely on—whatever was in effect on that date.
But how do you use them?
So if we want to convert a multibyte string to a wide character string, we can call the mbstowcs(). And the
other way around: wcstombs().
Let’s do a quick demo where we convert a multibyte string to a wide character string, and compare the string
lengths of the two using their respective functions.
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <wchar.h>
4 #include <string.h>
5 #include <locale.h>
6
7 int main(void)
8 {
9 // Get out of the C locale to one that likely has the euro symbol
10 setlocale(LC_ALL, "");
Chapter 27. Unicode, Wide Characters, and All That 199
11
19 // Convert the MB string to WC; this returns the number of wide chars
20 size_t wc_len = mbstowcs(wc_string, mb_string, 128);
21
(Your system might vary on the number of bytes depending on your locale.)
One interesting thing to note is that mbstowcs(), in addition to converting the multibyte string to wide,
returns the length (in characters) of the wide character string. On POSIX-compliant systems, you can take
advantage of a special mode where it only returns the length-in-characters of a given multibyte string: you just
pass NULL to the destination, and 0 to the maximum number of characters to convert (this value is ignored).
(In the code below, I’m using my extended source character set—you might have to replace those with \u
escapes.)
setlocale(LC_ALL, "");
printf("%zu", len_in_chars); // 7
27.9.1 wint_t
A lot of these functions use a wint_t to hold single characters, whether they are passed in or returned.
It is related to wchar_t in nature. A wint_t is an integer that can represent all values in the extended
character set, and also a special end-of-file character, WEOF.
This is used by a number of single-character-oriented wide character functions.
Chapter 27. Unicode, Wide Characters, and All That 200
Copying
Func-
tion Description
wcscpy() Copy string.
wcsncpy()Copy string, length-limited.
wmemcpy()Copy memory.
Copy potentially-overlapping memory.
wmemmove()
wcscat() Concatenate strings.
wcsncat()Concatenate strings, length-limited.
9
wcscoll() is the same as wcsxfrm() followed by wcscmp().
Chapter 27. Unicode, Wide Characters, and All That 202
Quick note on initializing an mbstate_t variable: just memset() it to zero. There is no built-in function to
force it to be initialized.
mbstate_t mbs;
Here is a list of the restartable conversion functions—note the naming convension of putting an “r” after the
“from” type:
• mbrtowc()—multibyte to wide character
• wcrtomb()—wide character to multibyte
• mbsrtowcs()—multibyte string to wide character string
• wcsrtombs()—wide character string to multibyte string
These are really similar to their non-restartable counterparts, except they require you pass in a pointer to your
own mbstate_t variable. And also they modify the source string pointer (to help you out if invalid bytes
are found), so it might be useful to save a copy of the original.
Here’s the example from earlier in the chapter reworked to pass in our own mbstate_t.
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <stddef.h>
4 #include <wchar.h>
5 #include <string.h>
6 #include <locale.h>
7
8 int main(void)
9 {
10 // Get out of the C locale to one that likely has the euro symbol
11 setlocale(LC_ALL, "");
12
33 // Convert the MB string to WC; this returns the number of wide chars
34 size_t wc_len = mbsrtowcs(wc_string, &invalid, 128, &mbs);
35
Chapter 27. Unicode, Wide Characters, and All That 204
36 if (invalid == NULL) {
37 printf("No invalid characters found\n");
38
For the conversion functions that manage their own state, you can reset their internal state to the initial one
by passing in NULL for their char* arguments, for example:
mbstowcs(NULL, NULL, 0); // Reset the parse state for mbstowcs()
mbstowcs(dest, src, 100); // Parse some stuff
For I/O, each wide stream manages its own mbstate_t and uses that for input and output conversions as it
goes.
And some of the byte-oriented I/O functions like printf() and scanf() keep their own internal state while
doing their work.
Finally, these restartable conversion functions do actually have their own internal state if you pass in NULL
for the mbstate_t parameter. This makes them behave more like their non-restartable counterparts.
27.11.1 UTF-8
To refresh before this section, read the UTF-8 quick note, above.
Aside from that, what are C’s UTF-8 capabilities?
Well, not much, unfortunately.
You can tell C that you specifically want a string literal to be UTF-8 encoded, and it’ll do it for you. You can
prefix a string with u8:
char *s = u8"Hello, world!";
Sure! If the extended source character set supports it. (gcc does.)
What if it doesn’t? You can specify a Unicode code point with your friendly neighborhood \u and \U, as
noted above.
But that’s about it. There’s no portable way in the standard library to take arbirary input and turn it into
UTF-8 unless your locale is UTF-8. Or to parse UTF-8 unless your locale is UTF-8.
So if you want to do it, either be in a UTF-8 locale and:
Chapter 27. Unicode, Wide Characters, and All That 205
setlocale(LC_ALL, "");
or figure out a UTF-8 locale name on your local machine and set it explicitly like so:
setlocale(LC_ALL, "en_US.UTF-8"); // Non-portable name
If you’re curious, and I know you are, the values, if UTF-16 or UTF-32, are stored in the native endianess.
That is, you should be able to compare them straight up to Unicode code point values:
char16_t pi = u"\u03C0"; // pi symbol
#if __STDC_UTF_16__
pi == 0x3C0; // Always true
#else
pi == 0x3C0; // Probably not true
#endif
All of these functions are restartable (i.e. you pass in your own mbstate_t), and all of them operate character
by character10 .
10
Ish—things get funky with multi-char16_t UTF-16 encodings.
11
https://en.wikipedia.org/wiki/Iconv
12
http://site.icu-project.org/
Chapter 28
Exiting a Program
Turns out there are a lot of ways to do this, and even ways to set up “hooks” so that a function runs when a
program exits.
In this chapter we’ll dive in and check them out.
We already covered the meaning of the exit status code in the Exit Status section, so jump back there and
review if you have to.
All the functions in this section are in <stdlib.h>.
28.1.2 exit()
This one has also made an appearance a few times. If you call exit() from anywhere in your program, it
will exit at that point.
The argument you pass to exit() is the exit status.
207
Chapter 28. Exiting a Program 208
4 void on_exit_1(void)
5 {
6 printf("Exit handler 1 called!\n");
7 }
8
9 void on_exit_2(void)
10 {
11 printf("Exit handler 2 called!\n");
12 }
13
14 int main(void)
15 {
16 atexit(on_exit_1);
17 atexit(on_exit_2);
18
19 printf("About to exit...\n");
20 }
4 void on_quick_exit_1(void)
5 {
6 printf("Quick exit handler 1 called!\n");
7 }
8
9 void on_quick_exit_2(void)
10 {
Chapter 28. Exiting a Program 209
14 void on_exit(void)
15 {
16 printf("Normal exit--I won't be called!\n");
17 }
18
19 int main(void)
20 {
21 at_quick_exit(on_quick_exit_1);
22 at_quick_exit(on_quick_exit_2);
23
28 quick_exit(0);
29 }
It works just like exit()/atexit(), except for the fact that file flushing and cleanup might not be done.
versus:
goats -= 100;
In that case, if I try to run it and goats falls under 0, this happens:
goat_counter: goat_counter.c:8: main: Assertion `goats >= 0' failed.
Aborted
This isn’t very user-friendly, so it’s only used for things the user will never see. And often people write their
own assert macros that can more easily be turned off.
1
https://en.wikipedia.org/wiki/Core_dump
Chapter 29
Signal Handling
Before we start, I’m just going to advise you to generally ignore this entire chapter and use your OS’s (very
likely) superior signal handling functions. Unix-likes have the sigaction() family of functions, and Win-
dows has… whatever it does1 .
With that out of the way, what are signals?
Signal Description
SIGABRT Abnormal termination—what happens when
abort() is called.
SIGFPE Floating point exception.
SIGILL Illegal instruction.
SIGINT Interrupt—usually the result of CTRL-C being hit.
SIGSEGV “Segmentation Violation”: invalid memory access.
SIGTERM Termination requested.
You can set up your program to ignore, handle, or allow the default action for each of these by using the
signal() function.
211
Chapter 29. Signal Handling 212
4 int main(void)
5 {
6 char s[1024];
7
Check out line 8—we tell the program to ignore SIGINT, the interrupt signal that’s raised when CTRL-C is
hit. No matter how much you hit it, the signal remains ignored. If you comment out line 8, you’ll see you
can CTRL-C with impunity and quit the program on the spot.
Basically, we’re going to pass in the signal number we’re interested in catching, and we’re going to pass a
pointer to a function of the form:
void f(int x);
returned
function indicates we're and
returns returning a that function
void pointer to function takes an int
|--| | |---|
void (*signal(int sig, void (*func)(int)))(int);
5 int count = 0;
6
21 if (count == 2) {
22 printf("Exiting!\n"); // Undefined behavior
23 exit(0);
24 }
25 }
26
27 int main(void)
28 {
29 signal(SIGINT, sigint_handler);
30
One of the things you’ll notice is that on line 14 we reset the signal handler. This is because C has the option
of resetting the signal handler to its SIG_DFL behavior before running your custom handler. In other words,
it could be a one-off. So we reset it first thing so that we handle it again for the next one.
We’re ignoring the return value from signal() in this case. If we’d set it to a different handler earlier, it
would return a pointer to that handler, which we could get like this:
Chapter 29. Signal Handling 214
void (*old_handler)(int);
That said, I’m not sure of a common use case for this. But if you need the old handler for some reason, you
can get it that way.
Quick note on line 16—that’s just to tell the compiler to not warn that we’re not using this variable. It’s like
saying, “I know I’m not using it; you don’t have to warn me.”
And lastly you’ll see that I’ve marked undefined behavior in a couple places. More on that in the next section.
Wikipedia goes so far as to say the only really portable thing you can do is call signal() with SIG_IGN or
SIG_DFL and that’s it.
Confusingly, the spec also says you can’t refer “to any object with static or thread storage duration that is not
a lock-free atomic object other than by assigning a value to an object declared as volatile sig_atomic_t
[…]”
My read on this is that you can’t read or write anything that’s not a lock-free atomic object. Also you can
assign to an object that’s volatile sig_atomic_t.
But can you read from it? I honestly don’t see why not, except that the spec is very pointed about mentioning
assigning into. But if you have to read it and make any kind of decision based on it, you might be opening
up room for some kind of race conditions.
With that in mind, we can rewrite our “hit CTRL-C twice to exit” code to be a little more portable, albeit less
verbose on the output.
Let’s change our SIGINT handler to do nothing except increment a value that’s of type volatile
sig_atomic_t. So it’ll count the number of CTRL-Cs that have been hit.
2
Confusingly, sig_atomic_t predates the lock-free atomics and is not the same thing.
3
If sig_action_t is signed, the range will be at least -127 to 127. If unsigned, at least 0 to 255.
Chapter 29. Signal Handling 215
Then in our main loop, we’ll check to see if that counter is over 2, then bail out if it is.
1 #include <stdio.h>
2 #include <signal.h>
3
15 int main(void)
16 {
17 signal(SIGINT, sigint_handler);
18
Undefined behavior again? It’s my read that this is, because we have to read the value in order to increment
and store it.
If we only want to postpone the exit by one hitting of CTRL-C, we can do that without too much trouble. But
any more postponement would require some ridiculous function chaining.
What we’ll do is handle it once, and the handler will reset the signal to its default behavior (that is, to exit):
1 #include <stdio.h>
2 #include <signal.h>
3
10 int main(void)
11 {
12 signal(SIGINT, sigint_handler);
13
16 while(1);
17 }
Later when we look at lock-free atomic variables, we’ll see a way to fix the count version (assuming lock-
free atomic variables are available on your particular system).
This is why at the beginning, I was suggesting checking out your OS’s built-in signal system as a probably-
superior alternative.
Chapter 29. Signal Handling 216
C provides a way for you to declare an array whose size is determined at runtime. This gives you the benefits
of dynamic runtime sizing like you get with malloc(), but without needing to worry about free()ing the
memory after.
Now, a lot of people don’t like VLAs. They’ve been banned from the Linux kernel, for example. We’ll dig
into more of that rationale later.
This is an optional feature of the language. The macro __STDC_NO_VLA__ is set to 1 if VLAs are not present.
(They were mandatory in C99, and then became optional in C11.)
#if __STDC_NO_VLA__ == 1
#error Sorry, need VLAs for this program!
#endif
But since neither GCC nor Clang bother to define this macro, you may get limited mileage from this.
Let’s dive in first with an example, and then we’ll look for the devil in the details.
But with VLAs, we can use a size determined at runtime to set the array, like this:
int n = 10;
int v[n];
Now, that looks like the same thing, and in many ways is, but this gives you the flexibility to compute the
size you need, and then get an array of exactly that size.
Let’s ask the user to input the size of the array, and then store the index-times-10 in each of those array
elements:
1 #include <stdio.h>
2
3 int main(void)
4 {
5 int n;
6
217
Chapter 30. Variable-Length Arrays (VLAs) 218
10 int v[n];
11
(On line 7, I have an fflush() that should force the line to output even though I don’t have a newline at the
end.)
Line 10 is where we declare the VLA—once execution gets past that line, the size of the array is set to
whatever n was at that moment. The array length can’t be changed later.
You can put an expression in the brackets, as well:
int v[x * 100];
Some restrictions:
• You can’t declare a VLA at file scope, and you can’t make a static one in block scope1 .
• You can’t use an initializer list to initialize the array.
Also, entering a negative value for the size of the array invokes undefined behavior—in this universe, anyway.
There’s a subtle and correct implication from the above line: pointer arithmetic works just like you’d expect
for a regular array. So go ahead and use it to your heart’s content:
1 #include <stdio.h>
2
3 int main(void)
4 {
5 int n = 5;
6 int v[n];
7
8 int *p = v;
9
10 *(p+2) = 12;
11 printf("%d\n", v[2]); // 12
1
This is due to how VLAs are typically allocated on the stack, whereas static variables are on the heap. And the whole idea with
VLAs is they’ll be automatically dellocated when the stack frame is popped at the end of the function.
Chapter 30. Variable-Length Arrays (VLAs) 219
12
13 p[3] = 34;
14 printf("%d\n", v[3]); // 34
15 }
Like with regular arrays, you can use parentheses with sizeof() to get the size of a would-be VLA without
actually declaring one:
int x = 12;
int x[h][w];
int y[5][w];
int z[10][w][20];
Again, you can navigate these just like you would a regular array.
10 return total;
11 }
12
13 int main(void)
14 {
15 int x[5]; // Standard array
16
17 int a = 5;
18 int y[a]; // VLA
19
25 }
But there’s a bit more to it than that. You can also let C know that the array is a specific VLA size by passing
that in first and then giving that dimension in the parameter list:
int sum(int count, int v[count])
{
// ...
}
Incidentally, there are a couple ways of listing a prototype for the above function; one of them involves an
* if you don’t want to specifically name the value in the VLA. It just indicates that the type is a VLA as
opposed to a regular pointer.
VLA prototypes:
void do_something(int count, int v[count]); // With names
void do_something(int, int v[*]); // Without names
Again, that * thing only works with the prototype—in the function itself, you’ll have to put the explicit size.
Now—let’s get multidimensional! This is where the fun begins.
12 int main(void)
13 {
14 int rows = 4;
15 int cols = 7;
16
17 int matrix[rows][cols];
18
12 int main(void)
13 {
14 int rec_count = 3;
15 int records[rec_count][5];
16
22 print_records(rec_count, records);
23 }
\\ ...
int w = 3, h = 5;
int matrix[h][w];
foo(matrix); // OK!
Likewise, if you have a VLA function, you can pass a regular array into it:
int foo(int h, int w, int m[h][w]) {...}
\\ ...
int matrix[3][5];
Beware, though: if your dimensions mismatch, you’re going to have some undefined behavior going on,
Chapter 30. Variable-Length Arrays (VLAs) 222
likely.
3 int main(void)
4 {
5 int w = 10;
6
16 // Print them
17 for (int i = 0; i < w; i++)
18 printf("%d\n", x[i]);
19
22 w = 20;
23
goto
The goto statement is universally revered and can be here presented without contest.
Just kidding! Over the years, there has been a lot of back-and-forth over whether or not (often not) goto is
considered harmful1 .
In this programmer’s opinion, you should use whichever constructs leads to the best code, factoring in main-
tainability and speed. And sometimes this might be goto!
In this chapter, we’ll see how goto works in C, and then check out some of the common cases where it is
used2 .
3 int main(void)
4 {
5 printf("One\n");
6 printf("Two\n");
7
8 goto skip_3;
9
10 printf("Three\n");
11
12 skip_3:
13
14 printf("Five!\n");
15 }
224
Chapter 31. goto 225
goto sends execution jumping to the specified label, skipping everything in between.
Labels are skipped over during execution. The following will print all three numbers in order just as if the
labels weren’t there:
printf("Zero\n");
label_1:
label_2:
printf("One\n");
label_3:
printf("Two\n");
label_4:
printf("Three\n");
As you’ve noticed, it’s common convention to justify the labels all the way on the left. This increases read-
ability because a reader can quickly scan to find the destination.
Labels have function scope. That is, no matter how many levels deep in blocks they appear, you can still
goto them from anywhere in the function.
It also means you can only goto labels that are in the same function as the goto itself. Labels in other
functions are out of scope from goto’s perspective. And it means you can use the same label name in two
functions—just not the same label name in the same function.
As we see, that continue, like all continues, goes to the next iteration of the nearest enclosing loop. What
if we want to continue in the next loop out, the loop with i?
Well, we can break to get back to the outer loop, right?
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
printf("%d, %d\n", i, j);
break; // Gets us to the next iteration of i
}
}
That gets us two levels of nested loop. But then if we nest another loop, we’re out of options. What about
this, where we don’t have any statement that will get us out to the next iteration of i?
Chapter 31. goto 226
}
}
}
We have a ; at the end there—that’s because you can’t have a label pointing to the plain end of a compound
statement (or before a variable declaration).
for(...) {
for (...) {
while (...) {
do {
if (some_error_condition)
goto bail;
} while(...);
}
}
}
bail:
// Cleanup here
Without goto, you’d have to check an error condition flag in all of the loops to get all the way out.
Chapter 31. goto 227
printf("Done!\n");
break_i:
printf("Done!\n");
if (init_system_2() == -1)
goto shutdown_1;
if (init_system_3() == -1)
goto shutdown_2;
if (init_system_4() == -1)
goto shutdown_3;
shutdown_system4();
shutdown_3:
shutdown_system3();
Chapter 31. goto 228
shutdown_2:
shutdown_system2();
shutdown_1:
shutdown_system1();
shutdown:
print("All subsystems shut down.\n");
Note that we’re shutting down in the reverse order that we initialized the subsystems. So if subsystem 4 fails
to start up, it will shut down 3, 2, then 1 in that order.
12 int main(void)
13 {
14 for (int i = 0; i < 8; i++)
15 printf("%d! == %ld\n", i, factorial(i, 1));
16 }
To make it happen, you can replace the call with two steps:
1. Set the values of the parameters to what they’d be on the next call.
2. goto a label on the first line of the function.
Let’s try it:
1 #include <stdio.h>
2
3
https://en.wikipedia.org/wiki/Tail_call
Chapter 31. goto 229
7 if (n == 0)
8 return a;
9
24 int main(void)
25 {
26 for (int i = 0; i < 8; i++)
27 printf("%d! == %d\n", i, factorial(i, 1));
28 }
I used temporary variables up there to set the next values of the parameters before jumping to the start of the
function. See how they correspond to the recursive arguments that were in the recursive call?
Now, why use temp variables? I could have done this instead:
a *= n;
n -= 1;
goto tco;
and that actually works just fine. But if I carelessly reverse those two lines of code:
n -= 1; // BAD NEWS
a *= n;
—now we’re in trouble. We modified n before using it to modify a. That’s Bad because that’s not how
it works when you call recursively. Using the temporary variables avoids this problem even if you’re not
looking out for it. And the compiler likely optimizes them out, anyway.
goto retry;
}
Many Unix-likes have an SA_RESTART flag you can pass to sigaction() to request the OS automatically
restart any slow syscalls instead of failing with EINTR.
Again, this is Unix-specific and is outside the C standard.
That said, it’s possible to use a similar technique any time any function should be restarted.
{
int x = 12345;
label:
printf("%d\n", x);
}
And then it prints out 0 when I run it (your mileage may vary).
Basically what has happened is that we jumped into x’s scope (so it was OK to reference it in the printf())
but we jumped over the line that actually initialized it to 12345. So the value was indeterminate.
The fix is, of course, to get the initialization after the label one way or another.
goto label;
{
int x;
label:
x = 12345;
printf("%d\n", x);
}
label:
printf("%d\n", x);
}
goto label;
Chapter 31. goto 231
goto label;
{
int v[x];
label:
printf("Hi!\n");
}
I get an error:
error: jump into scope of identifier with variably modified type
goto label;
{
label: ;
int v[x];
printf("Hi!\n");
}
Because that way the VLA gets allocated properly before its inevitable deallocation once it falls out of scope.
Chapter 32
This is the final chapter for types! We’re going to talk about two things:
• How to have “anonymous” unnamed objects and how that’s useful.
• How to generate type-dependent code.
They’re not particularly related, but don’t really each warrant their own chapters. So I crammed them in here
like a rebel!
Now, that line of code doesn’t do anything on its own. It creates an unnamed array of 4 ints, and then throws
them away without using them.
We could use a pointer to store a reference to the array…
int *p = (int []){1 ,2 ,3 ,4};
printf("%d\n", p[1]); // 2
But that seems a little like a long-winded way to have an array. I mean, we could have just done this1 :
int p[] = {1, 2, 3, 4};
printf("%d\n", p[1]); // 2
232
Chapter 32. Types Part V: Compound Literals and Generic Selections 233
return total;
}
If we wanted to call it, we’d normally have to do something like this, declaring an array and storing values
in it to pass to the function:
int a[] = {1, 2, 3, 4};
But unnamed objects give us a way to skip the variable by passing it directly in (parameter names listed
above). Check it out—we’re going to replace the variable a with an unnamed array that we pass in as the
first argument:
// p[] count
// |-----------------| |
int s = sum((int []){1, 2, 3, 4}, 4);
Pretty slick!
3 struct coord {
4 int x, y;
5 };
6
12 int main(void)
13 {
14 struct coord t = {.x=10, .y=20};
15
Straightforward enough?
Let’s modify it to use an unnamed object instead of the variable t we’re passing to print_coord().
Chapter 32. Types Part V: Compound Literals and Generic Selections 234
We’ll just take t out of there and replace it with an unnamed struct:
7 //struct coord t = {.x=10, .y=20};
8
Still works!
3 struct coord {
4 int x, y;
5 };
6
12 int main(void)
13 {
14 // Note the &
15 // |
16 print_coord(&(struct coord){.x=10, .y=20}); // prints "10, 20"
17 }
Additionally, this can be a nice way to pass even pointers to simple objects:
// Pass a pointer to an int with value 3490
foo(&(int){3490});
Easy as that.
{
p = &(int){10};
}
Chapter 32. Types Part V: Compound Literals and Generic Selections 235
Likewise, you can’t return a pointer to an unnamed object from a function. The object is deallocated when
it falls out of scope:
1 #include <stdio.h>
2
3 int *get3490(void)
4 {
5 // Don't do this
6 return &(int){3490};
7 }
8
9 int main(void)
10 {
11 printf("%d\n", *get3490()); // INVALID: (int){3490} fell out of scope
12 }
Just think of their scope like that of an ordinary local variable. You can’t return a pointer to a local variable,
either.
That last one is unnamed, but it’s silly. Might as well do the simple one on the line before.
But hopefully that provides a little more clarity on the syntax.
1 #include <stdio.h>
2
3 int main(void)
4 {
5 int i;
6 float f;
7 char c;
8
9 char *s = _Generic(i,
10 int: "that variable is an int",
11 float: "that variable is a float",
12 default: "that variable is some type"
13 );
14
15 printf("%s\n", s);
16 }
If the compiler can’t find a type match in the _Generic, it looks for the optional default case and uses that.
If it can’t find a type match and there’s no default, you’ll get a compile error. The first expression must
match one of the types or default.
Because it’s inconvenient to write _Generic over and over, it’s often used to make the body of a macro that
can be easily repeatedly reused.
Let’s make a macro TYPESTR(x) that takes an argument and returns a string with the type of the argument.
So TYPESTR(1) will return the string "int", for example.
Here we go:
#include <stdio.h>
int main(void)
{
int i;
long l;
float f;
double d;
char c;
Chapter 32. Types Part V: Compound Literals and Generic Selections 237
This outputs:
i is type int
l is type long
f is type float
d is type double
c is type something else
Which should be no surprise, because, like we said, that code in main() is replaced with the following when
it is compiled:
printf("i is type %s\n", "int");
printf("l is type %s\n", "long");
printf("f is type %s\n", "float");
printf("d is type %s\n", "double");
printf("c is type %s\n", "something else");
PRINT_VAL(i);
PRINT_VAL(s);
18 } while(0)
19
20 int main(void)
21 {
22 int i = 10;
23 float f = 3.14159;
24 char *s = "Hello, world!";
25
26 PRINT_VAL(i);
27 PRINT_VAL(f);
28 PRINT_VAL(s);
29 }
We could have crammed that all in one big macro, but I broke it into two to prevent eye bleeding.
Chapter 33
Arrays Part II
We’re going to go over a few extra misc things this chapter concerning arrays.
• Type qualifiers with array parameters
• The static keyword with array parameters
• Partial multi-dimensional array initializers
They’re not super-commonly seen, but we’ll peek at them since they’re part of the newer spec.
And you might also recall that you can add type qualifiers to a pointer variable like so:
int *const p;
int *volatile p;
int *const volatile p;
// etc.
But how can we do that when we’re using array notation in your parameter list?
Turns out it goes in the brackets. And you can put the optional count after. The two following lines are
equivalent:
int func(int *const volatile p) {...}
int func(int p[const volatile]) {...}
int func(int p[const volatile 10]) {...}
If you have a multidimensional array, you need to put the type qualifiers in the first set of brackets.
239
Chapter 33. Arrays Part II 240
What this means, in the above example, is the compiler is going to assume that any array you pass to the
function will be at least 4 elements.
Anything else is undefined behavior.
int func(int p[static 4]) {...}
int main(void)
{
int a[] = {11, 22, 33, 44};
int b[] = {11, 22, 33, 44, 55};
int c[] = {11, 22};
This basically sets the minimum size array you can have.
Important note: there is nothing in the compiler that prohibits you from passing in a smaller array. The
compiler probably won’t warn you, and it won’t detect it at runtime.
By putting static in there, you’re saying, “I double secret PROMISE that I will never pass in a smaller
array than this.” And the compiler says, “Yeah, fine,” and trusts you to not do it.
And then the compiler can make certain code optimizations, safe in the knowledge that you, the programmer,
will always do the right thing.
int main(void)
{
int a[3][2] = {
{1, 2},
{3, 4},
{5, 6}
};
Chapter 33. Arrays Part II 241
Let’s leave off some of the initializer elements and see they get set to zero:
int a[3][2] = {
{1, 2},
{3}, // Left off the 4!
{5, 6}
};
which produces:
1 2
3 0
5 6
And now we get this, which might not be what you expect:
1 2
5 6
0 0
But if you stop to think about it, we only provided enough initializers for two rows, so they got used for the
first two rows. And the remaining elements were initialized to zero.
So far so good. Generally, if we leave off parts of the initializer, the compiler sets the corresponding elements
to 0.
But let’s get crazy.
int a[3][2] = { 1, 2, 3, 4, 5, 6 };
So if you want to fill the whole array with 0, then go ahead and:
int a[3][2] = {0};
But my recommendation is if you have a 2D array, use a 2D initializer. It just makes the code more readable.
(Except for initializing the whole array with 0, in which case it’s idiomatic to use {0} no matter the dimension
of the array.)
Chapter 34
We’ve already seen goto, which jumps in function scope. But longjmp() allows you to jump back to an
earlier point in execution, back to a function that called this one.
There are a lot of limitations and caveats, but this can be a useful function for bailing out from deep in the
call stack back up to an earlier state.
In my experience, this is very rarely-used functionality.
4 jmp_buf env;
5
6 void depth2(void)
7 {
8 printf("Entering depth 2\n");
9 longjmp(env, 3490); // Bail out
10 printf("Leaving depth 2\n"); // This won't happen
11 }
12
13 void depth1(void)
14 {
15 printf("Entering depth 1\n");
16 depth2();
17 printf("Leaving depth 1\n"); // This won't happen
243
Chapter 34. Long Jumps with setjmp, longjmp 244
18 }
19
20 int main(void)
21 {
22 switch (setjmp(env)) {
23 case 0:
24 printf("Calling into functions, setjmp() returned 0\n");
25 depth1();
26 printf("Returned from functions\n"); // This won't happen
27 break;
28
29 case 3490:
30 printf("Bailed back to main, setjmp() returned 3490\n");
31 break;
32 }
33 }
If you try to take that output and match it up with the code, it’s clear there’s some really funky stuff going on.
One of the most notable things is that setjmp() returns twice. What the actual frank? What is this sorcery?!
So here’s the deal: if setjmp() returns 0, it means that you’ve successfully set the “bookmark” at that point.
If it returns non-zero, it means you’ve just returned to the “bookmark” set earlier. (And the value returned is
the one you pass to longjmp().)
This way you can tell the difference between setting the bookmark and returning to it later.
So when the code, above, calls setjmp() the first time, setjmp() stores the state in the env variable and
returns 0. Later when we call longjmp() with that same env, it restores the state and setjmp() returns the
value longjmp() was passed.
34.2 Pitfalls
Under the hood, this is pretty straightforward. Typically the stack pointer keeps track of the locations in
memory that local variables are stored, and the program counter keeps track of the address of the currently-
executing instruction1 .
So if we want to jump back to an earlier function, it’s basically only a matter of restoring the stack pointer and
program counter to the values kept in the jmp_buf variable, and making sure the return value is set correctly.
And then execution will resume there.
But a variety of factors confound this, making a significant number of undefined behavior traps.
Technically, they only have to be volatile if they change between the time setjmp() is called and
longjmp() is called2 .
if (setjmp(env) == 0) {
x = 30;
}
if (setjmp(env) == 0) {
x = 30;
}
Now the value will be the correct 30 after a longjmp() returns us to this point.
That’s too complex to be allowed by the spec due to the machinations that must occur when unrolling the stack
and all that. We can’t longjmp() back into some complex expression that’s only been partially executed.
So there are limits on the complexity of that expression.
• It can be the entire controlling expression of the conditional.
if (setjmp(env)) {...}
2
The rationale here is that the program might store a value temporarily in a CPU register while it’s doing work on it. In that timeframe,
the register holds the correct value, and the value on the stack might be out of date. Then later the register values would get overwritten
and the changes to the variable lost.
Chapter 34. Long Jumps with setjmp, longjmp 246
• It can be part of a relational or equality expression, as long as the other operand is an integer constant.
And the whole thing is the controlling expression of the conditional.
if (setjmp(env) == 0) {...}
• The operand to a logical NOT (!) operation, being the entire controlling expression.
if (!setjmp(env)) {...}
(void)setjmp(env);
3
That is, remain allocated until the program ends with no way to free it.
Chapter 35
Incomplete Types
int main(void)
{
struct foo *x;
union bar *y;
enum baz *z;
}
We never gave a size for a. And we have pointers to structs foo, bar, and baz that never seem to be
declared anywhere.
And the only warnings I get are that x, y, and z are unused.
These are examples of incomplete types.
An incomplete type is a type the size (i.e. the size you’d get back from sizeof) for which is not known.
Another way to think of it is a type that you haven’t finished declaring.
You can have a pointer to an incomplete type, but you can’t dereference it or use pointer arithmetic on it.
And you can’t sizeof it.
So what can you do with it?
247
Chapter 35. Incomplete Types 248
Even though the struct node is incomplete on line 3, we can still declare a pointer to one1 .
We can do the same thing if we have two different structs that refer to each other:
struct a {
struct b *x; // Refers to a `struct b`
};
struct b {
struct a *x; // Refers to a `struct a`
};
We’d never be able to make that pair of structures without the relaxed rules for incomplete types.
Most likely culprit: you probably forgot to #include the header file that declares the type.
If it’s a non-extern array with no size followed by an initializer, it’s incomplete until the closing brace of
the initializer.
1
This works because in C, pointers are the same size regardless of the type of data they point to. So the compiler doesn’t need to
know the size of the struct node at this point; it just needs to know the size of a pointer.
Chapter 35. Incomplete Types 249
3 #ifndef BAR_H
4 #define BAR_H
5
8 #endif
Then you can include the header from as many places as you’d like, and every one of those places will refer
to the same underlying my_array.
1 // File: foo.c
2
3 #include <stdio.h>
4 #include "bar.h" // includes the incomplete type for my_array
5
6 int main(void)
7 {
8 my_array[0] = 10;
9
10 printf("%d\n", my_array[0]);
11 }
When compiling multiple files, remember to specify all the .c files to the compiler, but not the .h files, e.g.:
gcc -o foo foo.c bar.c
struct foo {
int x, y, z;
}; // Now the struct foo is complete!
Chapter 35. Incomplete Types 250
Note that though void is an incomplete type, there’s no way to complete it. Not that anyone ever thinks of
doing that weird thing. But it does explain why you can do this:
void *p; // OK: pointer to incomplete type
Complex Numbers
Furthermore, there is a macro that indicates adherence to the ISO 60559 (IEEE 754) standard for floating
point math with complex numbers, as well as the presence of the _Imaginary type.
#if __STDC_IEC_559_COMPLEX__ != 1
#error Need IEC 60559 complex support!
#endif
More details on that are spelled out in Annex G in the C11 spec.
Those both mean the same thing, so you might as well use the prettier complex.
1
https://en.wikipedia.org/wiki/Complex_number
251
Chapter 36. Complex Numbers 252
You also get some types for imaginary numbers if you implementation is IEC 60559-compliant:
_Imaginary
imaginary
These also both mean the same thing, so you might as well use the prettier imaginary.
You also get values for the imaginary number 𝑖, itself:
I
_Complex_I
_Imaginary_I
The macro I is set to _Imaginary_I (if available), or _Complex_I. So just use I for the imaginary number.
One aside: I’ve said that if a compiler has __STDC_IEC_559_COMPLEX__ set to 1, it must support _Imag-
inary types to be compliant. That’s my read of the spec. However, I don’t know of a single compiler that
actually supports _Imaginary even though they have __STDC_IEC_559_COMPLEX__ set. So I’m going to
write some code with that type in here I have no way of testing. Sorry!
OK, so now we know there’s a complex type, how can we use it?
So that’s great for declarations, but how do we initialize them or assign to them?
Turns out we get to use some pretty natural notation. Example!
double complex x = 5 + 2*I;
double complex y = 10 + 3*I;
There’s also no problem using other floating point numbers to build it:
double a = 5;
double b = 2;
double complex x = a + b*I;
There is also a set of macros to help build these. The above code could be written using the CMPLX() macro,
like so:
Chapter 36. Complex Numbers 253
But the CMPLX() macro will handle negative zeros in the imaginary part correctly every time, whereas the
other way might convert them to positive zeros. I think2 This seems to imply that if there’s a chance the
imaginary part will be zero, you should use the macro… but someone should correct me on this if I’m
mistaken!
The CMPLX() macro works on double types. There are two other macros for float and long double:
CMPLXF() and CMPLXL(). (These “f” and “l” suffixes appear in virtually all the complex-number-related
functions.)
Now let’s try the reverse: if we have a complex number, how do we break it apart into its real and imaginary
parts?
Here we have a couple functions that will extract the real and imaginary parts from the number: creal()
and cimag():
double complex x = 5 + 2*I;
double complex y = 10 + 3*I;
Note that the i I have in the printf() format string is a literal i that gets printed—it’s not part of the format
specifier. Both return values from creal() and cimag() are double.
And as usual, there are float and long double variants of these functions: crealf(), cimagf(), cre-
all(), and cimagl().
4 int main(void)
5 {
6 double complex x = 1 + 2*I;
7 double complex y = 3 + 4*I;
8 double complex z;
9
10 z = x + y;
2
This was a harder one to research, and I’ll take any more information anyone can give me. I could be defined as _Complex_I or
_Imaginary_I, if the latter exists. _Imaginary_I will handle signed zeros, but _Complex_I might not. This has implications with
branch cuts and other complex-numbery-mathy things. Maybe. Can you tell I’m really getting out of my element here? In any case, the
CMPLX() macros behave as if I were defined as _Imaginary_I, with signed zeros, even if _Imaginary_I doesn’t exist on the system.
Chapter 36. Complex Numbers 254
13 z = x - y;
14 printf("x - y = %f + %fi\n", creal(z), cimag(z));
15
16 z = x * y;
17 printf("x * y = %f + %fi\n", creal(z), cimag(z));
18
19 z = x / y;
20 printf("x / y = %f + %fi\n", creal(z), cimag(z));
21 }
You can also compare two complex numbers for equality (or inequality):
1 #include <stdio.h>
2 #include <complex.h>
3
4 int main(void)
5 {
6 double complex x = 1 + 2*I;
7 double complex y = 3 + 4*I;
8
They are equal if both components test equal. Note that as with all floating point, they could be equal if
they’re close enough due to rounding error3 .
For example, the cabs() function for computing the absolute value of a complex number also has cabsf()
and cabsl() variants. I’m omitting them for brevity.
3
The simplicity of this statement doesn’t do justice to the incredible amount of work that goes into simply understanding how floating
point actually functions. https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/
Chapter 36. Complex Numbers 255
Function Description
ccos() Cosine
csin() Sine
ctan() Tangent
cacos() Arc cosine
casin() Arc sine
catan() Play Settlers of Catan
ccosh() Hyperbolic cosine
csinh() Hyperbolic sine
ctanh() Hyperbolic tangent
cacosh() Arc hyperbolic cosine
casinh() Arc hyperbolic sine
catanh() Arc hyperbolic tangent
Function Description
cexp() Base-𝑒 exponential
clog() Natural (base-𝑒) logarithm
Function Description
cabs() Absolute value
cpow() Power
csqrt() Square root
Function Description
creal() Return real part
cimag() Return imaginary part
CMPLX() Construct a complex number
carg() Argument/phase angle
conj() Conjugate4
cproj() Projection on Riemann sphere
4
This is the only one that doesn’t begin with an extra leading c, strangely.
Chapter 37
C has all those small, bigger, and biggest integer types like int and long and all that. And you can look in
the section on limits to see what the largest int is with INT_MAX and so on.
How big are those types? That is, how many bytes do they take up? We could use sizeof to get that answer.
But what if I wanted to go the other way? What if I needed a type that was exactly 32 bits (4 bytes) or at
least 16 bits or somesuch?
How can we declare a type that’s a certain size?
The header <stdint.h> gives us a way.
1
Some architectures have different sized data that the CPU and RAM can operate with at a faster rate than others. In those cases, if
you need the fastest 8-bit number, it might give you have a 16- or 32-bit type instead because that’s just faster. So with this, you won’t
know how big the type is, but it will be least as big as you say.
256
Chapter 37. Fixed Width Integer Types 257
int_fast8_t uint_fast8_t
int_fast16_t uint_fast16_t
int_fast32_t uint_fast32_t
int_fast64_t uint_fast64_t
There might be others of different widths, as well, but those are optional.
Hey! Where are the fixed types like int16_t? Turns out those are entirely optional…unless certain con-
ditions are met2 . And if you have an average run-of-the-mill modern computer system, those conditions
probably are met. And if they are, you’ll have these types:
int8_t uint8_t
int16_t uint16_t
int32_t uint32_t
int64_t uint64_t
Other variants with different widths might be defined, but they’re optional.
2
Namely, the system has 8, 16, 32, or 64 bit integers with no padding that use two’s complement representation, in which case the
intN_t variant for that particular number of bits must be defined.
Chapter 37. Fixed Width Integer Types 258
Note the MIN for all the unsigned types is 0, so, as such, there’s no macro for it.
Look for the patterns there. You can see there are variants for the fixed, least, fast, and max types.
And you also have a lowercase d and a lowercase i. Those correspond to the printf() format specifiers %d
and %i.
So if I have something of type:
int_least16_t x = 3490;
I can print that with the equivalent format specifier for %d by using PRId16.
But how? How do we use that macro?
First of all, that macro specifies a string containing the letter or letters printf() needs to use to print that
type. Like, for example, it could be "d" or "ld".
So all we need to do is embed that in our format string to the printf() call.
Chapter 37. Fixed Width Integer Types 259
To do this, we can take advantage of a fact about C that you might have forgotten: adjacent string literals are
automatically concatenated to a single string. E.g.:
printf("Hello, " "world!\n"); // Prints "Hello, world!"
And since these macros are string literals, we can use them like so:
1 #include <stdio.h>
2 #include <stdint.h>
3 #include <inttypes.h>
4
5 int main(void)
6 {
7 int_least16_t x = 3490;
8
Remember: when you want to print out a fixed size integer type with printf() or scanf(), grab the correct
corresponding format specifer from <inttypes.h>.
Chapter 38
260
Chapter 38. Date and Time Functionality 261
Great! You have a variable that gets you the time now.
Amusingly, there’s only one portable way to print out what’s in a time_t, and that’s the rarely-used ctime()
function that prints the value in local time:
now = time(NULL);
printf("%s", ctime(&now));
This returns a string with a very specific form that includes a newline at the end:
3
Admittedly, there are more than two.
4
https://en.wikipedia.org/wiki/Unix_time
Chapter 38. Date and Time Functionality 262
So that’s kind of inflexible. If you want more control, you should convert that time_t into a struct tm.
Once you have your time_t in a struct tm, it opens all kinds of doors. You can print out the time in a
variety of ways, figure out which day of the week a date is, and so on. Or convert it back into a time_t.
More on that soon!
time_t some_time_epoch;
some_time_epoch = mktime(&some_time);
printf("%s", ctime(&some_time_epoch));
printf("Is DST: %d\n", some_time.tm_isdst);
Output:
Mon Apr 12 12:00:04 1982
Is DST: 0
Chapter 38. Date and Time Functionality 263
When you manually load a struct tm like that, it should be in local time. mktime() will convert that local
time into a time_t calendar time.
Weirdly, however, the standard doesn’t give us a way to load up a struct tm with a UTC time and convert
that to a time_t. If you want to do that with Unix-likes, try the non-standard timegm(). On Windows,
_mkgmtime().
But what if I told you, dear reader, that there’s a way to have much more control over how the date was
printed?
Sure, we could fish individual fields out of the struct tm, but there’s a great function called strftime()
that will do a lot of the hard work for you. It’s like printf(), except for dates!
Let’s see some examples. In each of these, we pass in a destination buffer, a maximum number of characters
to write, and then a format string (in the style of—but not the same as—printf()) which tells strftime()
which components of a struct tm to print and how.
You can add other constant characters to include in the output in the format string, as well, just like with
printf().
We get a struct tm in this case from localtime(), but any source works fine.
1 #include <stdio.h>
2 #include <time.h>
3
4 int main(void)
5 {
6 char s[128];
7 time_t now = time(NULL);
8
There are a ton of date printing format specifiers for strftime(), so be sure to check them out in the
strftime() reference page5 .
Here’s an example where we get the time and print it out both as integer values and also a floating value:
struct timespec ts;
timespec_get(&ts, TIME_UTC);
Example output:
1614581530 s, 806325800 ns
1614581530.806326 seconds since epoch
struct timespec also makes an appearance in a number of the threading functions that need to be able to
specify time with that resolution.
5
https://beej.us/guide/bgclr/html/split/time.html#man-strftime
Chapter 38. Date and Time Functionality 265
4 int main(void)
5 {
6 struct tm time_a = {
7 .tm_year=82, // years since 1900
8 .tm_mon=3, // months since January -- [0, 11]
9 .tm_mday=12, // day of the month -- [1, 31]
10 .tm_hour=4, // hours since midnight -- [0, 23]
11 .tm_min=00, // minutes after the hour -- [0, 59]
12 .tm_sec=04, // seconds after the minute -- [0, 60]
13 .tm_isdst=-1, // Daylight Saving Time flag
14 };
15
16 struct tm time_b = {
17 .tm_year=120, // years since 1900
18 .tm_mon=10, // months since January -- [0, 11]
19 .tm_mday=15, // day of the month -- [1, 31]
20 .tm_hour=16, // hours since midnight -- [0, 23]
21 .tm_min=27, // minutes after the hour -- [0, 59]
22 .tm_sec=00, // seconds after the minute -- [0, 60]
23 .tm_isdst=-1, // Daylight Saving Time flag
24 };
25
Output:
1217996816.000000 seconds (38.596783 years) between events
And there you have it! Remember to use difftime() to take the time difference. Even though you can just
subtract on a POSIX system, might as well stay portable.
6
You will on POSIX, where time_t is definitely an integer. Unfortunately the entire world isn’t POSIX, so there we are.
Chapter 39
Multithreading
C11 introduced, formally, multithreading to the C language. It’s very eerily similar to POSIX threads1 , if
you’ve ever used those.
And if you’ve not, no worries. We’ll talk it through.
Do note, however, that I’m not intending this to be a full-blown classic multithreading how-to2 ; you’ll have
to pick up a different very thick book for that, specifically. Sorry!
Threading is an optional feature. If a C11+ compiler defines __STDC_NO_THREADS__, threads will not be
present in the library. Why they decided to go with a negative sense in that macro is beyond me, but there
we are.
You can test for it like this:
#ifdef __STDC_NO_THREADS__
#error I need threads to build this program!
#endif
Also, you might need to specify certain linker options when building. In the case of Unix-likes, try appending
a -lpthreads to the end of the command line to link the pthreads library3 :
gcc -std=c11 -o foo foo.c -lpthreads
If you’re getting linker errors on your system, it could be because the appropriate library wasn’t included.
39.1 Background
Threads are a way to have all those shiny CPU cores you paid for do work for you in the same program.
Normally, a C program just runs on a single CPU core. But if you know how to split up the work, you can
give pieces of it to a number of threads and have them do the work simultaneously.
Though the spec doesn’t say it, on your system it’s very likely that C (or the OS at its behest) will attempt to
balance the threads over all your CPU cores.
And if you have more threads than cores, that’s OK. You just won’t realize all those gains if they’re all trying
to compete for CPU time.
1
https://en.wikipedia.org/wiki/POSIX_Threads
2
I’m more a fan of shared-nothing, myself, and my skills with classic multithreading constructs are rusty, to say the least.
3
Yes, pthreads with a “p”. It’s short for POSIX threads, a library that C11 borrowed liberally from for its threads implementation.
266
Chapter 39. Multithreading 267
thrd_create() takes a pointer to the function to run, and it’s of type thrd_start_t, which is int
(*)(void *). That’s Greek for “a pointer to a function that takes an void* as an argument, and returns an
int.”
Let’s make a thread! We’ll launch it from the main thread with thrd_create() to run a function, do some
other things, then wait for it to complete with thrd_join(). I’ve named the thread’s main function run(),
but you can name it anything as long as the types match thrd_start_t.
1 #include <stdio.h>
2 #include <threads.h>
3
4 // This is the function the thread will run. It can be called anything.
5 //
6 // arg is the argument pointer passed to `thrd_create()`.
7 //
8 // The parent thread will get the return value back from `thrd_join()`'
9 // later.
10
20 int main(void)
21 {
22 thrd_t t; // t will hold the thread ID
23 int arg = 3490;
24
25 printf("Launching a thread\n");
26
38 // Wait here for the thread to complete; store the return value
39 // in res:
40
41 thrd_join(t, &res);
42
See how we did the thrd_create() there to call the run() function? Then we did other things in main()
and then stopped and waited for the thread to complete with thrd_join().
Chapter 39. Multithreading 269
The arg that you pass to the function has to have a lifetime long enough so that the thread can pick it up
before it goes away. Also, it needs to not be overwritten by the main thread before the new thread can use it.
Let’s look at an example that launches 5 threads. One thing to note here is how we use an array of thrd_ts
to keep track of all the thread IDs.
1 #include <stdio.h>
2 #include <threads.h>
3
10 return i;
11 }
12
13 #define THREAD_COUNT 5
14
15 int main(void)
16 {
17 thrd_t t[THREAD_COUNT];
18
19 int i;
20
21 printf("Launching threads...\n");
22 for (i = 0; i < THREAD_COUNT; i++)
23
When I run the threads, I count i up from 0 to 4. And pass a pointer to it to thrd_create(). This pointer
ends up in the run() routine where we make a copy of it.
Simple enough? Here’s the output:
Launching threads...
THREAD 2: running!
THREAD 3: running!
THREAD 4: running!
THREAD 2: running!
Doing other things while the thread runs...
Waiting for thread to complete...
Thread 2 complete!
Thread 2 complete!
THREAD 5: running!
Thread 3 complete!
Thread 4 complete!
Thread 5 complete!
All threads complete!
Whaaa—? Where’s THREAD 0? And why do we have a THREAD 5 when clearly i is never more than 4 when
we call thrd_create()? And two THREAD 2s? Madness!
This is getting into the fun land of race conditions. The main thread is modifying i before the thread has a
chance to copy it. Indeed, i makes it all the way to 5 and ends the loop before the last thread gets a chance
to copy it.
We’ve got to have a per-thread variable that we can refer to so we can pass it in as the arg.
We could have a big array of them. Or we could malloc() space (and free it somewhere—maybe in the
thread itself.)
Let’s give that a shot:
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <threads.h>
4
13 return i;
14 }
15
16 #define THREAD_COUNT 5
17
18 int main(void)
19 {
20 thrd_t t[THREAD_COUNT];
21
22 int i;
23
24 printf("Launching threads...\n");
Chapter 39. Multithreading 271
35 // ...
Notice on lines 27-30 we malloc() space for an int and copy the value of i into it. Each new thread gets
its own freshly-malloc()d variable and we pass a pointer to that to the run() function.
Once run() makes its own copy of the arg on line 7, it free()s the malloc()d int. And now that it has
its own copy, it can do with it what it pleases.
And a run shows the result:
Launching threads...
THREAD 0: running!
THREAD 1: running!
THREAD 2: running!
THREAD 3: running!
Doing other things while the thread runs...
Waiting for thread to complete...
Thread 0 complete!
Thread 1 complete!
Thread 2 complete!
Thread 3 complete!
THREAD 4: running!
Thread 4 complete!
All threads complete!
This removes the parent thread’s ability to get the return value from the child thread, but if you don’t care
about that and just want threads to clean up nicely on their own, this is the way to go.
Basically we’re going to do this:
thrd_create(&t, run, NULL);
thrd_detach(t);
where the thrd_detach() call is the parent thread saying, “Hey, I’m not going to wait for this child thread
to complete with thrd_join(). So go ahead and clean it up on your own when it completes.”
Chapter 39. Multithreading 272
1 #include <stdio.h>
2 #include <threads.h>
3
11 return 0;
12 }
13
14 #define THREAD_COUNT 10
15
16 int main(void)
17 {
18 thrd_t t;
19
Note that in this code, we put the main thread to sleep for 1 second with thrd_sleep()—more on that later.
Also in the run() function, I have a commented-out line in there that prints out the thread ID as an unsigned
long. This is non-portable, because the spec doesn’t say what type a thrd_t is under the hood—it could be
a struct for all we know. But that line works on my system.
Something interesting I saw when I ran the code, above, and printed out the thread IDs was that some threads
had duplicate IDs! This seems like it should be impossible, but C is allowed to reuse thread IDs after the
corresponding thread has exited. So what I was seeing was that some threads completed their run before
other threads were launched.
This can lead to race conditions, where you get Weird Things™ happening.
Check out this example. We have a static variable foo in block scope in run(). This variable will be
visible to all threads that pass through the run() function. And the various threads can effectively step on
each others toes.
Each thread copies foo into a local variable x (which is not shared between threads—all the threads have
their own call stacks). So they should be the same, right?
And the first time we print them, they are6 . But then right after that, we check to make sure they’re still the
6
Though I don’t think they have to be. It’s just that the threads don’t seem to get rescheduled until some system call like might
happen with a printf()… which is why I have the printf() in there.
Chapter 39. Multithreading 273
same.
And they usually are. But not always!
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <threads.h>
4
9 free(arg);
10
26 if (x != foo) {
27 printf("Thread %d: Craziness! x != foo! %d != %d\n", n, x, foo);
28 }
29
32 return 0;
33 }
34
35 #define THREAD_COUNT 5
36
37 int main(void)
38 {
39 thrd_t t[THREAD_COUNT];
40
In thread 1, between the two printf()s, the value of foo somehow changed from 10 to 11, even though
clearly there’s no increment between the printf()s!
It was another thread that got in there (probably thread 0, from the look of it) and incremented the value of
foo behind thread 1’s back!
Let’s solve this problem two different ways. (If you want all the threads to share the variable and not step on
each other’s toes, you’ll have to read on to the mutex section.)
9 free(arg);
10
One additional feature is that these functions allow you to specify a destructor that will be called on the data
when the TSS variable is deleted. Commonly this destructor is free() to automatically clean up malloc()d
per-thread data. Or NULL if you don’t need to destroy anything.
The destructor is type tss_dtor_t which is a pointer to a function that returns void and takes a void* as
an argument (the void* points to the data stored in the variable). In other words, it’s a void (*)(void*),
if that clears it up. Which I admit it probably doesn’t. Check out the example, below.
Generally, thread_local is probably your go-to, but if you like the destructor idea, then you can make use
of that.
The usage is a bit weird in that we need a variable of type tss_t to be alive to represent the value on a per
thread basis. Then we initialize it with tss_create(). Eventually we get rid of it with tss_delete().
Note that calling tss_delete() doesn’t run all the destructors—it’s thrd_exit() (or returning from the
run function) that does that. tss_delete() just releases any memory allocated by tss_create().
In the middle, threads can call tss_set() and tss_get() to set and get the value.
In the following code, we set up the TSS variable before creating the threads, then clean up after the threads.
In the run() function, the threads malloc() some space for a string and store that pointer in the TSS variable.
When the thread exits, the destructor function (free() in this case) is called for all the threads.
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <threads.h>
4
5 tss_t str;
6
7 void some_function(void)
8 {
9 // Retrieve the per-thread value of this string
10 char *tss_string = tss_get(str);
11
12 // And print it
13 printf("TSS string: %s\n", tss_string);
14 }
15
34 #define THREAD_COUNT 15
Chapter 39. Multithreading 276
35
36 int main(void)
37 {
38 thrd_t t[THREAD_COUNT];
39
Again, this is kind of a painful way of doing things compared to thread_local, so unless you really need
that destructor functionality, I’d use that instead.
39.7 Mutexes
If you want to only allow a single thread into a critical section of code at a time, you can protect that section
with a mutex7 .
For example, if we had a static variable and we wanted to be able to get and set it in two operations without
another thread jumping in the middle and corrupting it, we could use a mutex for that.
You can acquire a mutex or release it. If you attempt to acquire the mutex and succeed, you may continue
execution. If you attempt and fail (because someone else holds it), you will block8 until the mutex is released.
If multiple threads are blocked waiting for a mutex to be released, one of them will be chosen to run (at
random, from our perspective), and the others will continue to sleep.
The gameplan is that first we’ll initialize a mutex variable to make it ready to use with mtx_init().
Then subsequent threads can call mtx_lock() and mtx_unlock() to get and release the mutex.
When we’re completely done with the mutex, we can destroy it with mtx_destroy(), the logical opposite
of mtx_init().
First, let’s look at some code that does not use a mutex, and endeavors to print out a shared (static) serial
number and then increment it. Because we’re not using a mutex over the getting of the value (to print it) and
the setting (to increment it), threads might get in each other’s way in that critical section.
1 #include <stdio.h>
2 #include <threads.h>
3
7
Short for “mutual exclusion”, AKA a “lock” on a section of code that only one thread is permitted to execute.
8
That is, your process will go to sleep.
Chapter 39. Multithreading 277
6 (void)arg;
7
12 serial++;
13
14 return 0;
15 }
16
17 #define THREAD_COUNT 10
18
19 int main(void)
20 {
21 thrd_t t[THREAD_COUNT];
22
Clearly multiple threads are getting in there and running the printf() before anyone gets a change to update
the serial variable.
What we want to do is wrap the getting of the variable and setting of it into a single mutex-protected stretch
of code.
We’ll add a new variable to represent the mutex of type mtx_t in file scope, initialize it, and then the threads
can lock and unlock it in the run() function.
1 #include <stdio.h>
2 #include <threads.h>
3
19 serial++;
20
21 // Done getting and setting the data, so free the lock. This will
22 // unblock threads on the mtx_lock() call:
23
26 return 0;
27 }
28
29 #define THREAD_COUNT 10
30
31 int main(void)
32 {
33 thrd_t t[THREAD_COUNT];
34
See how on lines 38 and 50 of main() we initialize and destroy the mutex.
But each individual thread acquires the mutex on line 15 and releases it on line 24.
In between the mtx_lock() and mtx_unlock() is the critical section, the area of code where we don’t want
multiple threads mucking about at the same time.
And now we get proper output!
Thread running! 0
Thread running! 1
Thread running! 2
Chapter 39. Multithreading 279
Thread running! 3
Thread running! 4
Thread running! 5
Thread running! 6
Thread running! 7
Thread running! 8
Thread running! 9
If you need multiple mutexes, no problem: just have multiple mutex variables.
And always remember the Number One Rule of Multiple Mutexes: Unlock mutexes in the opposite order in
which you lock them!
Type Description
mtx_plain Regular ol’ mutex
mtx_timed Mutex that supports timeouts
mtx_plain|mtx_recursive Recursive mutex
mtx_timed|mtx_recursive Recursive mutex that supports timeouts
“Recursive” means that the holder of a lock can call mtx_lock() multiple times on the same lock. (They
have to unlock it an equal number of times before anyone else can take the mutex.) This might ease coding
from time to time, especially if you call a function that needs to lock the mutex when you already hold the
mutex.
And the timeout gives a thread a chance to try to get the lock for a while, but then bail out if it can’t get it in
that timeframe.
For a timeout mutex, be sure to create it with mtx_timed:
mtx_init(&serial_mtx, mtx_timed);
And then when you wait for it, you have to specify a time in UTC when it will unlock9 .
The function timespec_get() from <time.h> can be of assistance here. It’ll get you the current time in
UTC in a struct timespec which is just what we need. In fact, it seems to exist merely for this purpose.
It has two fields: tv_sec has the current time in seconds since epoch, and tv_nsec has the nanoseconds
(billionths of a second) as the “fractional” part.
So you can load that up with the current time, and then add to it to get a specific timeout.
Then call mtx_timedlock() instead of mtx_lock(). If it returns the value thrd_timedout, it timed out.
struct timespec timeout;
9
You might have expected it to be “time from now”, but you’d just like to think that, wouldn’t you!
Chapter 39. Multithreading 280
if (result == thrd_timedout) {
printf("Mutex lock timed out!\n");
}
Other than that, timed locks are the same as regular locks.
And indeed they don’t! There’s some behind-the-scenes magic with condition variables: when you
cnd_wait(), it releases the mutex that you specify and the thread goes to sleep. And when someone signals
that thread to wake up, it reacquires the lock as if nothing had happened.
It’s a little different on the cnd_signal() side of things. This doesn’t do anything with the mutex. The
signaling thread still must manually release the mutex before the waiting threads can wake up.
One more thing on the cnd_wait(). You’ll probably be calling cnd_wait() if some condition10 is not yet
met (e.g. in this case, if not all the numbers have yet been entered). Here’s the deal: this condition should be
in a while loop, not an if statement. Why?
It’s because of a mysterious phenomenon called a spurious wakeup. Sometimes, in some implementations, a
thread can be woken up out of a cnd_wait() sleep for seemingly no reason. [X-Files music]11 . And so we
have to check to see that the condition we need is still actually met when we wake up. And if it’s not, back
to sleep with us!
So let’s do this thing! Starting with the main thread:
• The main thread will set up the mutex and condition variable, and will launch the child thread.
• Then it will, in an infinite loop, get numbers as input from the console.
• It will also acquire the mutex to store the inputted number into a global array.
• When the array has 5 numbers in it, the main thread will signal the child thread that it’s time to wake
up and do its work.
• Then the main thread will unlock the mutex and go back to reading the next number from the console.
Meanwhile, the child thread has been up to its own shenanigans:
• The child thread grabs the mutex
• While the condition is not met (i.e. while the shared array doesn’t yet have 5 numbers in it), the child
thread sleeps by waiting on the condition variable. When it waits, it implicitly unlocks the mutex.
• Once the main thread signals the child thread to wake up, it wakes up to do the work and gets the mutex
lock back.
• The child thread sums the numbers and resets the variable that is the index into the array.
• It then releases the mutex and runs again in an infinite loop.
And here’s the code! Give it some study so you can see where all the above pieces are being handled:
1 #include <stdio.h>
2 #include <threads.h>
3
4 #define VALUE_COUNT_MAX 5
5
10
And that’s why they’re called condition variables!
11
I’m not saying it’s aliens… but it’s aliens. OK, really more likely another thread might have been woken up and gotten to the work
first.
Chapter 39. Multithreading 282
15
16 for (;;) {
17 mtx_lock(&value_mtx); // <-- GRAB THE MUTEX
18
24 printf("Thread: is awake!\n");
25
26 int t = 0;
27
28 // Add everything up
29 for (int i = 0; i < VALUE_COUNT_MAX; i++)
30 t += value[i];
31
40 return 0;
41 }
42
43 int main(void)
44 {
45 thrd_t t;
46
54 mtx_init(&value_mtx, mtx_plain);
55 cnd_init(&value_cnd);
56
57 for (;;) {
58 int n;
59
60 scanf("%d", &n);
61
64 value[value_count++] = n;
65
66 if (value_count == VALUE_COUNT_MAX) {
67 printf("Main: signaling thread\n");
68 cnd_signal(&value_cnd); // <-- SIGNAL CONDITION
Chapter 39. Multithreading 283
69 }
70
77 mtx_destroy(&value_mtx);
78 cnd_destroy(&value_cnd);
79 }
And here’s some sample output (individual numbers on lines are my input):
Thread: is waiting
1
1
1
1
1
Main: signaling thread
Thread: is awake!
Thread: total is 5
Thread: is waiting
2
8
5
9
0
Main: signaling thread
Thread: is awake!
Thread: total is 24
Thread: is waiting
It’s a common use of condition variables in producer-consumer situations like this. If we didn’t have a way
to put the child thread to sleep while it waited for some condition to be met, it would be force to poll which
is a big waste of CPU.
if (result == thrd_timedout) {
printf("Condition variable timed out!\n");
}
void run_once_function(void)
{
printf("I'll only run once!\n");
}
call_once(&of, run_once_function);
// ...
In this example, no matter how many threads get to the run() function, the run_once_function() will
only be called a single time.
12
Survival of the fittest! Right? I admit it’s actually nothing like that.
Chapter 40
Atomics
If those tests pass, then you can safely include <stdatomic.h>, the header on which the rest of this chapter
is based. But if there is no atomic support, that header might not even exist.
On some systems, you might need to add -latomic to the end of your compilation command line to use any
functions in the header file.
285
Chapter 40. Atomics 286
That is, the other thread will see the entire write of, say, a 32-bit value. Not half of it. There’s no way for
one thread to interrupt another that is in the middle of an atomic multi-byte write.
It’s almost like there’s a little lock around the getting and setting of that one variable. (And there might be!
See Lock-Free Atomic Variables, below.)
And on that note, you can get away with never using atomics if you use mutexes to lock your critical sections.
It’s just that there are a class of lock-free data structures that always allow other threads to make progress
instead of being blocked by a mutex… but these are tough to create correctly from scratch, and are one of
the things that are beyond the scope of the guide, sadly.
That’s only part of the story. But it’s the part we’ll start with.
Before we go further, how do you declare a variable to be atomic?
First, include <stdatomic.h>.
This gives us types such as atomic_int.
And then we can simply declare variables to be of that type.
But let’s do a demo where we have two threads. The first runs for a while and then sets a variable to a specific
value, then exits. The other runs until it sees that value get set, and then it exits.
1 #include <stdio.h>
2 #include <threads.h>
3 #include <stdatomic.h>
4
17 printf("Thread 1: Exiting\n");
18 return 0;
19 }
20
32 int main(void)
33 {
34 x = 0;
Chapter 40. Atomics 287
35
41 thrd_join(t1, NULL);
42 thrd_join(t2, NULL);
43
The second thread spins in place, looking at the flag and waiting for it to get set to the value 3490. And the
first one does that.
And I get this output:
Thread 1: Sleeping for 1.5 seconds
Thread 2: Waiting for 3490
Thread 1: Setting x to 3490
Thread 1: Exiting
Thread 2: Got 3490--exiting!
Main : Threads are done, so x better be 3490
Main : And indeed, x == 3490
Look, ma! We’re accessing a variable from different threads and not using a mutex! And that’ll work every
time thanks to the atomic nature of atomic variables.
You might be wondering what happens if that’s a regular non-atomic int, instead. Well, on my system it
still works… unless I do an optimized build in which case it hangs on thread 2 waiting to see the 3490 to get
set2 .
But that’s just the beginning of the story. The next part is going to require more brain power and has to do
with something called synchronization.
40.3 Synchronization
The next part of our story is all about when certain memory writes in one thread become visible to those in
another thread.
You might think, it’s right away, right? But it’s not. A number of things can go wrong. Weirdly wrong.
The compiler might have rearranged memory accesses so that when you think you set a value relative to
another might not be true. And even if the compiler didn’t, your CPU might have done it on the fly. Or
maybe there’s something else about this architecture that causes writes on one CPU to be delayed before
they’re visible on another.
The good news is that we can condense all these potential troubles into one: unsynchronized memory accesses
can appear out of order depending on which thread is doing the observing, as if the lines of code themselves
had been rearranged.
By way of example, which happens first in the following code, the write to x or the write to y?
2
The reason for this is when optimized, my compiler has put the value of x in a register to make the while loop fast. But the
register has no way of knowing that the variable was updated in another thread, so it never sees the 3490. This isn’t really related to the
all-or-nothing part of atomicity, but is more related to the synchronization aspects in the next section.
Chapter 40. Atomics 288
1 int x, y; // global
2
3 // ...
4
5 x = 2;
6 y = 3;
7
Answer: we don’t know. The compiler or CPU could silently reverse lines 5 and 6 and we’d be none-the-
wiser. The code would run single-threaded as-if it were executed in code order.
In a multithreaded scenario, we might have something like this pseudocode:
1 int x = 0, y = 0;
2
3 thread1() {
4 x = 2;
5 y = 3;
6 }
7
8 thread2() {
9 while (y != 3) {} // spin
10 printf("x is now %d\n", x); // 2? ...or 0?
11 }
But something sneaky could rearrange lines 4 and 5 causing us to see the value of 0 for x when we print it.
In other words, all bets are off unless we can somehow say, “As of this point, I expect all previous writes in
another thread to be visible in this thread.”
Two threads synchronize when they agree on the state of shared memory. As we’ve seen, they’re not always
in agreement with the code. So how do they agree?
Using atomic variables can force the agreement3 . If a thread writes to an atomic variable, it’s saying “anyone
who reads this atomic variable in the future will also see all the changes I made to memory (atomic or not)
up to and including the atomic variable”.
Or, in more human terms, let’s sit around the conference table and make sure we’re on the same page as
to which pieces of shared memory hold what values. You agree that the memory changes that you’d made
up-to-and-including the atomic store will be visible to me after I do a load of the same atomic variable.
So we can easily fix our example:
1 int x = 0;
2 atomic int y = 0; // Make y atomic
3
4 thread1() {
5 x = 2;
6 y = 3; // Synchronize on write
7 }
8
3
Until I say otherwise, I’m speaking generally about sequentially consistent operations. More on what that means soon.
Chapter 40. Atomics 289
9 thread2() {
10 while (y != 3) {} // Synchronize on read
11 printf("x is now %d\n", x); // 2, period.
12 }
Because the threads synchronize across y, all writes in thread 1 that happened before the write to y are visible
in thread 2 after the read from y (in the while loop).
It’s important to note a couple things here:
1. Nothing sleeps. The synchronization is not a blocking operation. Both threads are running full bore
until they exit. Even the one stuck in the spin loop isn’t blocking anyone else from running.
2. The synchronization happens when one thread reads an atomic variable another thread wrote. So when
thread 2 reads y, all previous memory writes in thread 1 (namely setting x) will be visible in thread 2.
3. Notice that x isn’t atomic. That’s OK because we’re not synchronizing over x, and the synchronization
over y when we write it in thread 1 means that all previous writes—including x—in thread 1 will
become visible to other threads… if those other threads read y to synchronize.
Forcing this synchronization is inefficient and can be a lot slower than just using a regular variable. This is
why we don’t use atomics unless we have to for a particular application.
So that’s the basics. Let’s look deeper.
• The acquire acts as a one-way barrier when it comes to code reordering; reads and writes in the current
thread can be moved down from before the acquire to after it. But, more importantly for synchroniza-
tion, nothing can move up from after the acquire to before it.
With write/store/release of a particular atomic variable:
• All writes (atomic or non-atomic) in the current thread that happened before this release become visible
to other threads that have read/loaded/acquired the same atomic variable.
• The value written to this atomic variable by this thread is also visible to other threads.
• No reads or writes of any variables/memory in the current thread can be reordered to happen after this
release.
• The release acts as a one-way barrier when it comes to code reordering: reads and writes in the current
thread can be moved up from after the release to before it. But, more importantly for synchronization,
nothing can move down from before the release to after it.
Again, the upshot is synchronization of memory from one thread to another. The second thread can be sure
that variables and memory are written in the order the programmer intended.
int x, y, z = 0;
atomic_int a = 0;
thread1() {
x = 10;
y = 20;
a = 999; // Release
z = 30;
}
thread2()
{
while (a != 999) { } // Acquire
In the above example, thread2 can be sure of the values in x and y after it acquires a because they were set
before thread1 released the atomic a.
But thread2 can’t be sure of z’s value because it happened after the release. Maybe the assignment to z got
moved before the assignment to a.
An important note: releasing one atomic variable has no effect on acquires of different atomic variables.
Each variable is isolated from the others.
memory orderings.
All the stuff we’ve been talking about so far has happened within the realm of sequential consistency.
We’ve talked about how the compiler or CPU can rearrange memory reads and writes in a single thread as
long as it follows the as-if rule.
And we’ve seen how we can put the brakes on this behavior by synchronizing over atomic variables.
Let’s formalize just a little more.
If operations are sequentially consistent, it means at the end of the day, when all is said and done, all the
threads can kick up their feet, open their beverage of choice, and all agree on the order in which memory
changes occurred during the run. And that order is the one specified by the code.
One won’t say, “But didn’t B happen before A?” if the rest of them say, “A definitely happened before B”.
They’re all friends, here.
In particular, within a thread, none of the acquires and releases can be reordered with respect to one another.
This is in addition to the rules about what other memory accesses can be reordered around them.
This rule gives an additional level of sanity to the progression of atomic loads/acquires and stores/releases.
Every other memory order in C involves a relaxation of the reordering rules, either for acquires/releases or
other memory accesses, atomic or otherwise. You’d do that if you really knew what you were doing and
needed the speed boost. Here be armies of dragons…
More on that later, but for now, let’s stick to the safe and practical.
thread1() {
x = x + 3; // NOT atomic!
}
Since there’s a read of x on the right hand side of the assignment and a write effectively on the left, these are
two operations. Another thread could sneak in the middle and make you unhappy.
But you can use the shorthand += to get an atomic operation:
atomic_int x = 0;
thread1() {
x += 3; // ATOMIC!
}
In that case, x will be atomically incremented by 3—no other thread can jump in the middle.
In particular, the following operators are atomic read-modify-write operations with sequential consistency,
so use them with gleeful abandon. (In the example, a is atomic.)
a++ a-- --a ++a
a += b a -= b a *= b a /= b a %= b
a &= b a |= b a ^= b a >>= b a <<= b
Chapter 40. Atomics 292
call_once()—Synchronizes with all subsequent calls to call_once() for a particular flag. This way
subsequent calls can rest assured that if another thread sets the flag, they will see it.
thrd_create()—Synchronizes with the beginning of the new thread. The new thread can be sure it will
see all shared memory writes from the parent thread from before the thrd_create() call.
thrd_join()—When a thread dies, it synchronizes with this function. The thread that has called
thrd_join() can be assured that it can see all the late thread’s shared writes.
mtx_lock()—Earlier calls to mtx_unlock() on the same mutex synchronize on this call. This is the case
that most mirrors the acquire/release process we’ve already talked about. mtx_unlock() performs a release
on the mutex variable, assuring any subsequent thread that makes an acquire with mtx_lock() can see all
the shared memory changes in the critical section.
mtx_timedlock() and mtx_trylock()—Similar to the situation with mtx_lock(), if this call succeeds,
earlier calls to mtx_unlock() synchronize with this one.
Dynamic Memory Functions: if you allocate memory, it synchronizes with the previous deallocation of
that same memory. And allocations and deallocations of that particular memory region happen in a single
total order that all threads can agree upon. I think the idea here is that the deallocation can wipe the region if
it chooses, and we want to be sure that a subsequent allocation doesn’t see the non-wiped data. Someone let
me know if there’s more to it.
Use those at will! They’re consistent with the atomic aliases found in C++, if that helps.
But what if you want more?
You can do it either with a type qualifier or type specifier.
First, specifier! It’s the keyword _Atomic with a type in parens after5 —suitable for use with typedef:
typedef _Atomic(double) atomic_double;
atomic_double f;
Restrictions on the specifier: the type you’re making atomic can’t be of type array or function, nor can it be
atomic or otherwise qualified.
Next, qualifier! It’s the keyword _Atomic without a type in parens.
So these do similar things6 :
_Atomic(int) i; // type specifier
_Atomic int j; // type qualifier
The thing is, you can include other type qualifiers with the latter:
_Atomic volatile int k; // qualified atomic variable
Restrictions on the qualifier: the type you’re making atomic can’t be of type array or function.
5
Apparently C++23 is adding this as a macro.
6
The spec notes that they might differ in size, representation, and alignment.
Chapter 40. Atomics 294
Value Meaning
0 Never lock-free.
1 Sometimes lock-free.
2 Always lock-free.
Wait—how can something be sometimes lock-free? This just means the answer isn’t known at compile-time,
but could later be known at runtime. Maybe the answer varies depending on whether or not you’re running
this code on Genuine Intel or AMD, or something like that7 .
But you can always test at runtime with the atomic_is_lock_free() function. This function returns true
or false if the particular type is atomic right now.
So why do we care?
Lock-free is faster, so maybe there’s a speed concern that you’d code around another way. Or maybe you
need to use an atomic variable in a signal handler.
7
I just pulled that example out of nowhere. Maybe it doesn’t matter on Intel/AMD, but it could matter somewhere, dangit!
Chapter 40. Atomics 295
You can set the flag atomically with atomic_flag_test_and_set(), which will set the flag and return its
previous status as a _Bool (true for set).
You can clear the flag atomically with atomic_flag_clear().
Here’s an example where we init the flag to clear, set it twice, then clear it again.
#include <stdio.h>
#include <stdbool.h>
#include <stdatomic.h>
atomic_flag f = ATOMIC_FLAG_INIT;
int main(void)
{
bool r = atomic_flag_test_and_set(&f);
printf("Value was: %d\n", r); // 0
r = atomic_flag_test_and_set(&f);
printf("Value was: %d\n", r); // 1
atomic_flag_clear(&f);
r = atomic_flag_test_and_set(&f);
printf("Value was: %d\n", r); // 0
}
If there’s not a lot of data in there (i.e. a handful of bytes), the resulting atomic type might be lock-free. Test
it with atomic_is_lock_free().
1 #include <stdio.h>
2 #include <stdatomic.h>
3
4 int main(void)
5 {
6 struct point {
7 float x, y;
8 };
9
10 _Atomic(struct point) p;
11
Here’s the catch: you can’t access fields of an atomic struct or union… so what’s the point? Well, you
can atomically copy the entire struct into a non-atomic variable and then use it. You can atomically copy
the other way, too.
1 #include <stdio.h>
2 #include <stdatomic.h>
3
4 int main(void)
5 {
6 struct point {
7 float x, y;
8 };
9
10 _Atomic(struct point) p;
11 struct point t;
12
17 t = p; // Atomic copy
18
You can also declare a struct where individual fields are atomic. It is implementation defined if atomic
types are allowed on bitfields.
p = &x; // OK!
Chapter 40. Atomics 297
Second, atomic pointers to non-atomic values (i.e. the pointer value itself is atomic, but the thing it points to
is not):
int x;
int * _Atomic p; // p is an atomic pointer to an int
p = &x; // OK!
Lastly, atomic pointers to atomic values (i.e. the pointer and the thing it points to are both atomic):
_Atomic int x;
_Atomic int * _Atomic p; // p is an atomic pointer to an atomic int
p = &x; // OK!
memory_order Description
memory_order_seq_cst Sequential Consistency
memory_order_acq_rel Acquire/Release
memory_order_release Release
memory_order_acquire Acquire
memory_order_consume Consume
memory_order_relaxed Relaxed
You can specify other ones with certain library functions. For example, you can add a value to an atomic
variable like this:
atomic_int x = 0;
atomic_fetch_add_explicit(&x, 5, memory_order_seq_cst);
But what if we didn’t want sequential consistency? And you wanted acquire/release instead for whatever
reason? Just name it:
atomic_int x = 0;
atomic_fetch_add_explicit(&x, 5, memory_order_acq_rel);
We’ll do a breakdown of the different memory orders, below. Don’t mess with anything other than sequential
consistency unless you know what you’re doing. It’s really easy to make mistakes that will cause rare, hard-
to-repro failures.
Chapter 40. Atomics 298
40.13.2 Acquire
This is what happens on a load/read operation on an atomic variable.
• If another thread released this atomic variable, all the writes that thread did are now visible in this
thread.
• Memory accesses in this thread that happen after this load can’t be reordered before it.
40.13.3 Release
This is what happens on a store/write of an atomic variable.
• If another thread later acquires this atomic variable, all memory writes in this thread before its atomic
write become visible to that other thread.
• Memory accesses in this thread that happen before the release can’t be reordered after it.
40.13.4 Consume
This is an odd one, similar to a less-strict version of acquire. It affects memory accesses that are data depen-
dent on the atomic variable.
Being “data dependent” vaguely means that the atomic variable is used in a calculation.
That is, if a thread consumes an atomic variable then all the operations in that thread that go on to use that
atomic variable will be able to see the memory writes in the releasing thread.
Compare to acquire where memory writes in the releasing thread will be visible to all operations in the current
thread, not just the data-dependent ones.
Also like acquire, there is a restriction on which operations can be reordered before the consume. With
acquire, you couldn’t reorder anything before it. With consume, you can’t reorder anything that depends on
the loaded atomic value before it.
40.13.5 Acquire/Release
This only applies to read-modify-write operations. It’s an acquire and release bundled into one.
• An acquire happens for the read.
• A release happens for the write.
40.13.6 Relaxed
No rules; it’s anarchy! Everyone can reorder everything everywhere! Dogs and cats living together—mass
hysteria!
Actually, there is a rule. Atomic reads and writes are still all-or-nothing. But the operations can be reordered
whimsically and there is zero synchronization between threads.
Chapter 40. Atomics 299
There are a few use cases for this memory order, which you can find with a tiny bit of searching, e.g. simple
counters.
And you can use a fence to force synchronization after a bunch of relaxed writes.
40.14 Fences
You know how the releases and acquires of atomic variables occur as you read and write them?
Well, it’s possible to do a release or acquire without an atomic variable, as well.
This is called a fence. So if you want all the writes in a thread to be visible elsewhere, you can put up a
release fence in one thread and an acquire fence in another, just like with how atomic variables work.
Since a consume operation doesn’t really make sense on a fence10 , memory_order_consume is treated as an
acquire.
You can put up a fence with any specified order:
atomic_thread_fence(memory_order_release);
There’s also a light version of a fence for use with signal handlers, called atomic_signal_fence().
It works just the same way as atomic_thread_fence(), except:
• It only deals with visibility of values within the same thread; there is no synchronization with other
threads.
• No hardware fence instructions are emitted.
If you want to be sure the side effects of non-atomic operations (and relaxed atomic operations) are visible
in the signal handler, you can use this fence.
The idea is that the signal handler is executing in this thread, not another, so this is a lighter-weight way of
making sure changes outside the signal handler are visible within it (i.e. they haven’t been reordered).
40.15 References
If you want to learn more about this stuff, here are some of the things that helped me plow through it:
• Herb Sutter’s atomic<> Weapons talk:
– Part 111
– part 212
• Jeff Preshing’s materials13 , in particular:
– An Introduction to Lock-Free Programming14
– Acquire and Release Semantics15
– The Happens-Before Relation16
– The Synchronizes-With Relation17
– The Purpose of memory_order_consume in C++1118
10
Because consume is all about the operations that are dependent on the value of the acquired atomic variable, and there is no atomic
variable with a fence.
11
https://www.youtube.com/watch?v=A8eCGOqgvH4
12
https://www.youtube.com/watch?v=KeLBd2EJLOU
13
https://preshing.com/archives/
14
https://preshing.com/20120612/an-introduction-to-lock-free-programming/
15
https://preshing.com/20120913/acquire-and-release-semantics/
16
https://preshing.com/20130702/the-happens-before-relation/
17
https://preshing.com/20130823/the-synchronizes-with-relation/
18
https://preshing.com/20140709/the-purpose-of-memory_order_consume-in-cpp11/
Chapter 40. Atomics 300
19
https://preshing.com/20150402/you-can-do-any-kind-of-atomic-read-modify-write-operation/
20
https://en.cppreference.com/w/c/atomic/memory_order
21
https://en.cppreference.com/w/c/language/atomic
22
https://docs.microsoft.com/en-us/windows/win32/dxtecharts/lockless-programming
23
https://www.reddit.com/r/C_Programming/
Chapter 41
These don’t see a heck of a lot of use in my experience, but we’ll cover them here for the sake of completeness.
This is meant to encourage the compiler to make this function call as fast as possible. And, historically, one
way to do this was inlining, which means that the body of the function would be embedded in its entirety
where the call was made. This would avoid all the overhead of setting up the function call and tearing it
down at the expense of larger code size as the function was copied all over the place instead of being reused.
That would seem to be the end of the story, but it’s not. inline comes with a whole pile of rules that make
for interesting times. I’m not sure I even understand them all, and behavior seems to vary from compiler to
compiler.
The short answer is define the inline function as static in the file that you need it. And then use it in that
one file. And you never have to worry about the rest of it.
But if you’re wondering, here are more fun times.
Let’s try leaving the static off.
1 #include <stdio.h>
2
301
Chapter 41. Function Specifiers, Alignment Specifiers/Operators 302
8 int main(void)
9 {
10 printf("%d\n", add(1, 2));
11 }
gcc gives a linker error on add()… unless you compile with optimizations on (probably)!
See, a compiler can choose to inline or not, but if it chooses not to, you’re left with no function at all. gcc
doesn’t inline unless you’re doing an optimized build.
One way around this is to define a non-inline external linkage version of the function elsewhere, and that
one will be used when the inline one isn’t. But you as the programmer can’t determine which, portably. If
both are available, it’s unspecified which one the compiler chooses. With gcc the inline function will be used
if you’re compiling with optimizations, and the non-inline one will be used otherwise. Even if the bodies of
these functions are completely different. Zany!
Another way is to declare the function as extern inline. This will attempt to inline in this file, but will
also create a version with external linkage. And so gcc will use one or the other depending on optimizations,
but at least they’re the same function.
Unless, of course, you have another source file with an inline function of the same name; it will use its
inline function or the one with external linkage depending on optimizations.
But let’s say you’re doing a build where the compiler is inlining the function. In that case, you can just use
a plain inline in the definition. However, there are now additional restrictions.
You can’t refer to any static globals:
static int b = 13;
return x + y + b;
}
return x + y + b;
}
Now, you know the functions are extern by default, so we should be able to call add() from another file.
You’d like to think that, wouldn’t you!
But you can’t! If it’s just a plain inline, it’s similar to static: it’s only visible in that file.
Okay, so what if you throw an extern on there? Now we’re coming full circle to when we discussed having
inline mixed with functions with external linkage.
Chapter 41. Function Specifiers, Alignment Specifiers/Operators 303
3 #include <stdio.h>
4
10 int main(void)
11 {
12 printf("foo.c: %s\n", func());
13
14 void bar(void);
15 bar();
16 }
Recall that unless we’re doing an optimized build with gcc. func() will vanish and we’ll get a linker error.
Unless, or course, we have a version with external linkage defined elsewhere.
And we do. In bar.c.
1 // bar.c
2
3 #include <stdio.h>
4
5 char *func(void)
6 {
7 return "bar's function";
8 }
9
10 void bar(void)
11 {
12 printf("bar.c: %s\n", func());
13 }
And if I compile with gcc with optimizations1 it will use inline functions, and we’ll get the expected:
foo.c: foo's function
bar.c: bar's function
Great!
But if we compile in gcc without optimizations, it ignores the inline function and uses the external linkage
func() from bar.c! And we get this:
1
You can do this with -O on the command line.
Chapter 41. Function Specifiers, Alignment Specifiers/Operators 304
In short, the rules are surprisingly complex. I give myself a good 30% chance of having described them
correctly.
12 int main(void)
13 {
14 foo();
15 }
If the compiler detects that a noreturn function could return, it might warn you, helpfully.
Replacing the foo() function with this:
noreturn void foo(void)
{
printf("Breakin' the law\n");
}
gets me a warning:
foo.c:7:1: warning: function declared 'noreturn' should not return
2
https://beej.us/guide/bgclr/html/split/stdlib.html#man-exit
3
https://beej.us/guide/bgclr/html/split/stdlib.html#man-abort
Chapter 41. Function Specifiers, Alignment Specifiers/Operators 305
You can also pass a constant value or expression in for the alignment. This has to be something supported
by the system, but the spec stops short of dictating what values you can put in there. Small powers of 2 (1,
2, 4, 8, and 16) are generally safe bets.
char alignas(8) c; // align on 8-byte boundaries
If you want to align at the maximum used alignment by your system, include <stddef.h> and use the type
max_align_t, like so:
char alignas(max_align_t) c;
You could potentially over-align by specifying an alignment more than that of max_align_t, but whether
or not such things are allowed is system dependent.
5 struct t {
6 int a;
7 char b;
8 float c;
9 };
10
11 int main(void)
12 {
13 printf("char : %zu\n", alignof(char));
4
https://en.wikipedia.org/wiki/Data_structure_alignment
Chapter 41. Function Specifiers, Alignment Specifiers/Operators 306
Output on my system:
char : 1
short : 2
int : 4
long : 8
long long : 8
double : 8
long double: 16
struct t : 16
max_align_t: 16
307
INDEX 308