Modern C
By Jens Gustedt
()
About this ebook
Modern C focuses on the new and unique features of modern C programming. The book is based on the latest C standards and offers an up-to-date perspective on this tried-and-true language.
About the technology
C is extraordinarily modern for a 50-year-old programming language. Whether you’re writing embedded code, low-level system routines, or high-performance applications, C is up to the challenge. This unique book, based on the latest C standards, exposes a modern perspective of this tried-and-true language.
About the book
Modern C introduces you to modern day C programming, emphasizing the unique and new features of this powerful language. For new C coders, it starts with fundamentals like structure, grammar, compilation, and execution. From there, you’ll advance to control structures, data types, operators, and functions, as you gain a deeper understanding of what’s happening under the hood. In the final chapters, you’ll explore performance considerations, reentrancy, atomicity, threads, and type-generic programming. You’ll code as you go with concept-reinforcing exercises and skill-honing challenges along the way.
What's inside
Operators and functions
Pointers, threading, and atomicity
C’s memory model
Hands-on exercises
About the reader
For programmers comfortable writing simple programs in a language like Java, Python, Ruby, C#, C++, or C.
About the author
Jens Gustedt is a senior scientist at the French National Institute for Computer Science and Control (INRIA) and co-editor of the ISO C standard.
Jens Gustedt
Jens Gustedt has been a senior scientist at the French National Institute for Computer Science and Control (INRIA) since 1998, working in areas including algorithms, scientific experimentation, models for coarse-grained parallelism, and distributed locking. Currently, he’s conducting the Modular C project, which has given rise to libraries such as arbogast and EiLck.
Related to Modern C
Related ebooks
Scala in Action Rating: 0 out of 5 stars0 ratingsReal-World Functional Programming: With examples in F# and C# Rating: 0 out of 5 stars0 ratingsNode.js in Action Rating: 0 out of 5 stars0 ratingsMetaprogramming in .NET Rating: 5 out of 5 stars5/5The Little Elixir & OTP Guidebook Rating: 0 out of 5 stars0 ratingsFunctional Programming in Kotlin Rating: 0 out of 5 stars0 ratingsParallel and High Performance Computing Rating: 0 out of 5 stars0 ratingsFunctional Programming in C#, Second Edition Rating: 0 out of 5 stars0 ratingsGo in Practice Rating: 4 out of 5 stars4/5Deep Learning with JavaScript: Neural networks in TensorFlow.js Rating: 0 out of 5 stars0 ratingsC++ Concurrency in Action Rating: 4 out of 5 stars4/5Seriously Good Software: Code that works, survives, and wins Rating: 5 out of 5 stars5/5Dependency Injection Principles, Practices, and Patterns Rating: 5 out of 5 stars5/5The Well-Grounded Rubyist Rating: 0 out of 5 stars0 ratingsModern Java in Action: Lambdas, streams, functional and reactive programming Rating: 2 out of 5 stars2/5Event Processing in Action Rating: 0 out of 5 stars0 ratings.NET Core in Action Rating: 0 out of 5 stars0 ratingsFunctional Programming in C#: How to write better C# code Rating: 5 out of 5 stars5/5Functional Programming in Scala Rating: 4 out of 5 stars4/5Programming with Types: Examples in TypeScript Rating: 0 out of 5 stars0 ratingsFunctional Programming in JavaScript: How to improve your JavaScript programs using functional techniques Rating: 0 out of 5 stars0 ratingsFunctional Reactive Programming Rating: 0 out of 5 stars0 ratingsCross-Platform Desktop Applications: Using Node, Electron, and NW.js Rating: 0 out of 5 stars0 ratingsNode.js in Practice Rating: 0 out of 5 stars0 ratingsElixir in Action Rating: 0 out of 5 stars0 ratingsTiny C Projects Rating: 0 out of 5 stars0 ratingsGo Web Programming Rating: 5 out of 5 stars5/5Redis in Action Rating: 0 out of 5 stars0 ratingsWPF in Action with Visual Studio 2008: Covers Visual Studio 2008 Service Pack 1 and .NET 3.5 Service Pack 1! Rating: 0 out of 5 stars0 ratingsLINQ in Action Rating: 0 out of 5 stars0 ratings
Operating Systems For You
Excel : The Ultimate Comprehensive Step-By-Step Guide to the Basics of Excel Programming: 1 Rating: 5 out of 5 stars5/5Linux: Learn in 24 Hours Rating: 5 out of 5 stars5/5The Linux Command Line Beginner's Guide Rating: 4 out of 5 stars4/5The Windows Command Line Beginner's Guide: Second Edition Rating: 4 out of 5 stars4/5Windows 11 All-in-One For Dummies Rating: 5 out of 5 stars5/5Windows 11 For Dummies Rating: 0 out of 5 stars0 ratingsmacOS Sonoma For Dummies Rating: 0 out of 5 stars0 ratingsTor Darknet Bundle: Master the Art of Invisibility Rating: 0 out of 5 stars0 ratingsMacBook Pro User Manual: 2022 MacBook Pro User Guide for beginners and seniors to Master Macbook Pro like a Pro Rating: 0 out of 5 stars0 ratingsLinux Command-Line Tips & Tricks Rating: 0 out of 5 stars0 ratingsMake Your PC Stable and Fast: What Microsoft Forgot to Tell You Rating: 4 out of 5 stars4/5OneNote: The Ultimate Guide on How to Use Microsoft OneNote for Getting Things Done Rating: 1 out of 5 stars1/5iPhone Unlocked Rating: 0 out of 5 stars0 ratingsUbuntu Linux Bible Rating: 0 out of 5 stars0 ratingsiPhone For Dummies Rating: 0 out of 5 stars0 ratingsLearn Windows PowerShell in a Month of Lunches Rating: 0 out of 5 stars0 ratingsApple Card and Apple Pay: A Ridiculously Simple Guide to Mobile Payments Rating: 0 out of 5 stars0 ratingsRaspberry Pi Cookbook for Python Programmers Rating: 0 out of 5 stars0 ratingsCompTIA Linux+ Study Guide: Exam XK0-004 Rating: 0 out of 5 stars0 ratingsBash Command Line Pro Tips Rating: 5 out of 5 stars5/5Easy Linux For Beginners Rating: 2 out of 5 stars2/5Networking for System Administrators: IT Mastery, #5 Rating: 5 out of 5 stars5/5CompTIA A+ Complete Study Guide: Core 1 Exam 220-1101 and Core 2 Exam 220-1102 Rating: 0 out of 5 stars0 ratingsAndroid Security Cookbook Rating: 0 out of 5 stars0 ratingsHacking : The Ultimate Comprehensive Step-By-Step Guide to the Basics of Ethical Hacking Rating: 5 out of 5 stars5/5The iPadOS 17: The Complete User Manual to Quick Set Up and Mastering the iPadOS 17 with New Features, Pictures, Tips, and Tricks Rating: 0 out of 5 stars0 ratingsRaspberry Pi for Secret Agents - Second Edition Rating: 3 out of 5 stars3/5Linux Bible Rating: 0 out of 5 stars0 ratings
Reviews for Modern C
0 ratings0 reviews
Book preview
Modern C - Jens Gustedt
Level 0. Encounter
Our mascot for this level is the magpie, one of the most intelligent nonhuman species on earth. They are capable of elaborate social rituals and usage of tools.
This first level of the book may be your first encounter with the programming language C. It provides you with a rough knowledge about C programs, their purpose, their structure, and how to use them. It is not meant to give you a complete overview, it can’t and it doesn’t even try. On the contrary, it is supposed to give you a general idea of what this is all about, open up questions, and promote ideas and concepts. These then will be explained in detail in the higher levels.
Chapter 1. Getting started
This chapter covers
Introduction to imperative programming
Compiling and running code
In this chapter, I will introduce you to one simple program that has been chosen because it contains many of the constructs of the C language. If you already have programming experience, you may find that parts of it feel like needless repetition. If you lack such experience, you might feel overwhelmed by the stream of new terms and concepts.
In either case, be patient. For those of you with programming experience, it’s very possible that there are subtle details you’re not aware of, or assumptions you have made about the language that are not valid, even if you have programmed C before. For those approaching programming for the first time, be assured that after approximately 10 pages your understanding will have increased a lot, and you should have a much clearer idea of what programming represents.
An important bit of wisdom for programming in general, and for this book in particular, is summarized in the following citation from the Hitchhiker’s Guide to the Galaxy by Douglas Adams [1986]:
Takeaway B
Don’t panic.
It’s not worth it. There are many cross references, links, and bits of side information in the text, and there is an index at the end. Follow those if you have a question. Or just take a break.
Programming in C is about having the computer complete some specific tasks. A C program does that by giving orders, much as we would express such orders in the imperative tense in many human languages; thus the term imperative programming for this particular way of organizing computer programs. To get started and see what we are talking about, consider our first program in listing 1.1:
Listing 1.1. A first example of a C program
1 /* This may look like nonsense, but really is -*- mode: C -*- */
2 #include
3 #include
4
5 /* The main thing that this program does. */
6 int main(void
) {
7 // Declarations
8 double A
[5] = {
9 [0] = 9.0, 10 [1] = 2.9, 11 [4] = 3.E+25, 12 [3] = .00007, 13 }; 14 15 // Doing some work 16 for (size_t i = 0; i < 5; ++i) { 17 printf(element %zu is %g, \tits square is %g\n
, 18 i, 19 A[i], 20 A[i]*A[i]); 21 } 22 23 return EXIT_SUCCESS; 24 }
1.1. Imperative programming
You probably see that this is a sort of language, containing some weird words like main, include, for, and so on, which are laid out and colored in a peculiar way and mixed with a lot of strange characters, numbers, and text ("Doing some work) that looks like ordinary English. It is designed to provide a link between us, the human programmers, and a machine, the computer, to tell it what to do: to give it
orders."
Takeaway 1.1
C is an imperative programming language.
In this book, we will not only encounter the C programming language, but also some vocabulary from an English dialect, C jargon, the language that helps us to talk about C. It will not be possible to immediately explain each term the first time it occurs. But I will explain each one in time, and all of them are indexed so you can easily cheat and jumpC to more explanatory text, at your own risk.[¹]
¹ Such special terms from C jargon are marked with a C, as shown here.
As you can probably guess from this first example, such a C program has different components that form some intermixed layers. Let’s try to understand it from the inside out. The visible result of running this program is to output 5 lines of text on the command terminal of your computer. On my computer, using this program looks something like this:
Terminal
0 > ./getting-started 1 element 0 is 9, its square is 81 2 element 1 is 2.9, its square is 8.41 3 element 2 is 0, its square is 0 4 element 3 is 7e-05, its square is 4.9e-09 5 element 4 is 3e+25, its square is 9e+50
We can easily identify within our program the parts of the text that this program outputs (printsC, in C jargon): the part of line 17 between quotes. The real action happens between that line and line 20. C calls this a statementC, which is a bit of a misnomer. Other languages would use the term instruction, which describes the purpose better. This particular statement is a callC to a functionC named printf:
getting-started.c
17 printf(element %zu is %g, \tits square is %g\n
, 18 i, 19 A[i], 20 A[i]*A[i]);
Here, the printf function receives four argumentsC, enclosed in a pair of parenthesesC, ( ... ):
The funny-looking text (between the quotes) is a so-called string literalC that serves as a formatC for the output. Within the text are three markers (format specifiersC) that indicate the positions in the output where numbers are to be inserted. These markers start with a % character. This format also contains some special escape charactersC that start with a backslash: \t and \n.
After a comma character, we find the word i. The thing i stands for will be printed in place of the first format specifier, %zu.
Another comma separates the next argument A[i]. The thing this stands for will be printed in place of the second format specifier, the first %g.
Last, again separated by a comma, appears A[i]*A[i], corresponding to the last %g.
We will later explain what all of these arguments mean. Just remember that we identified the main purpose of the program (to print some lines on the terminal) and that it orders
the printf function to fulfill that purpose. The rest is some sugarC to specify which numbers will be printed, and how many of them.
1.2. Compiling and running
As shown in the previous section, the program text expresses what we want our computer to do. As such, it is just another piece of text that we have written and stored somewhere on our hard disk, but the program text as such cannot be understood by your computer. There is a special program, called a compiler, that translates the C text into something that your machine can understand: the binary codeC or executableC. What that translated program looks like and how this translation is done are much too complicated to explain at this stage.[²] Even this entire book will not be able to explain most of it; that would be the subject of another whole book. However, for the moment, we don’t need to understand more deeply, as we have the tool that does all the work for us.
² In fact, the translation itself is done in several steps that go from textual replacement, over proper compilation, to linking. Nevertheless, the tool that bundles all this is traditionally called a compiler and not a translator, which would be more accurate.
Takeaway 1.2
C is a compiled programming language.
The name of the compiler and its command-line arguments depend a lot on the platformC on which you will be running your program. There is a simple reason for this: the target binary code is platform dependentC: that is, its form and details depend on the computer on which you want to run it. A PC has different needs than a phone, and your refrigerator doesn’t speak the same language
as your set-top box. In fact, that’s one of the reasons for C to exist: C provides a level of abstraction for all the different machine-specific languages (usually referred to as assemblerC).
Takeaway 1.3
A correct C program is portable between different platforms.
In this book, we will put a lot of effort into showing you how to write correct
C programs that ensure portability. Unfortunately, there are some platforms that claim to be C
but do not conform to the latest standards; and there are conforming platforms that accept incorrect programs or provide extensions to the C standard that are not widely portable. So, running and testing a program on a single platform will not always guarantee portability.
It is the job of the compiler to ensure that the little program shown earlier, once translated for the appropriate platform, will run correctly on your PC, your phone, your set-top box, and maybe even your refrigerator.
That said, if you have a POSIX system (such as Linux or macOS), there is a good chance that a program named c99 might be present and that it is in fact a C compiler. You could try to compile the example program using the following command:
Terminal
0 > c99 -Wall -o getting-started getting-started.c -lm
The compiler should do its job without complaining and output an executable file called getting-started in your current directory.[[Exs 1]] In the example line,
[Exs 1] Try the compilation command in your terminal.
c99 is the compiler program.
-Wall tells it to warn us about anything that it finds unusual.
-o getting-started tells it to store the compiler outputC in a file named getting-started.
getting-started.c names the source fileC, the file that contains the C code we have written. Note that the .c extension at the end of the filename refers to the C programming language.
-lm tells it to add some standard mathematical functions if necessary; we will need those later on.
Now we can executeC our newly created executableC. Type in
Terminal
0 > ./getting-started
and you should see exactly the same output as I showed you earlier. That’s what portable means: wherever you run that program, its behaviorC should be the same.
If you are not lucky and the compilation command didn’t work, you will have to look up the name of your compilerC in your system documentation. You might even have to install a compiler if one is not available.[³] The names of compilers vary. Here are some common alternatives that might do the trick:
³ This is necessary in particular if you have a system with a Microsoft operating system. Microsoft’s native compilers do not yet fully support even C99, and many features that we discuss in this book will not work. For a discussion of the alternatives, you might have a look at Chris Wellons’ blog entry Four Ways to Compile C for Windows
(https://nullprogram.com/blog/2016/06/13/).
Terminal
0 > clang -Wall -lm -o getting-started getting-started.c 1 > gcc -std=c99 -Wall -lm -o getting-started getting-started.c 2 > icc -std=c99 -Wall -lm -o getting-started getting-started.c
Some of these, even if they are present on your computer, might not compile the program without complaining.[[Exs 2]]
[Exs 2] Start writing a text report about your tests with this book. Note down which command worked for you.
With the program in listing 1.1, we presented an ideal world: a program that works and produces the same result on all platforms. Unfortunately, when programming yourself, very often you will have a program that works only partially and that may produce wrong or unreliable results. Therefore, let us look at the program in listing 1.2. It looks quite similar to the previous one.
Listing 1.2. An example of a C program with flaws
1 /* This may look like nonsense, but really is -*- mode: C -*- */
2
3 /* The main thing that this program does. */
4 void main
() {
5 // Declarations
6 int i
;
7 double A
[5] = {
8
9.0,
9 2.9, 10 3.E+25, 11 .00007, 12 }; 13 14 // Doing some work 15 for (i = 0; i < 5; ++i) { 16 printf(element %d is %g, \tits square is %g\n
, 17 i, 18 A[i], 19 A[i]*A[i]); 20 } 21 22 return 0; 23 }
If you run your compiler on this program, it should give you some diagnosticC information similar to this:
Terminal
0 > c99 -Wall -o bad bad.c 1 bad.c:4:6: warning: return type of 'main' is not 'int' [-Wmain] 2 bad.c: In function 'main': 3 bad.c:16:6: warning: implicit declaration of function 'printf' [-Wimplicit-function... 4 bad.c:16:6: warning: incompatible implicit declaration of built-in function 'printf' ... 5 bad.c:22:3: warning: 'return' with a value, in function returning void [enabled by de...
Here we had a lot of long warning
lines that are even too long to fit on a terminal screen. In the end, the compiler produced an executable. Unfortunately, the output when we run the program is different. This is a sign that we have to be careful and pay attention to details.
clang is even more picky than gcc and gives us even longer diagnostic lines:
Terminal
0
> clang -Wall -o getting-started-badly bad.c
1
bad.c:4:1: warning: return type of 'main' is not 'int' [-Wmain-return-type]
2
void main() {
3
^
4
bad.c:16:6: warning: implicitly declaring library function 'printf' with type
5
'int (const char *, ...)'
6
printf(element %d is %g, \tits square is %g\n
, /*@\label{printf-start-badly}*/
7
^
8
bad.c:16:6: note: please include the header
9 for 'printf' 10 bad.c:22:3: error: void function 'main' should not return a value [-Wreturn-type] 11 return 0; 12 ^ ~ 13 2 warnings and 1 error generated.
This is a good thing! Its diagnostic outputC is much more informative. In particular, it gave us two hints: it expected a different return type for main, and it expected us to have a line such as line 3 from listing 1.1 to specify where the printf function comes from. Notice how clang, unlike gcc, did not produce an executable. It considers the problem on line 22 fatal. Consider this to be a feature.
Depending on your platform, you can force your compiler to reject programs that produce such diagnostics. For gcc, such a command-line option would be -Werror.
So we have seen two of the points in which listings 1.1 and 1.2 differed, and these two modifications turned a good, standards-conforming, portable program into a bad one. We also have seen that the compiler is there to help us. It nailed the problem down to the lines in the program that cause trouble, and with a bit of experience you will be able to understand what it is telling you.[[Exs 3]] [[Exs 4]]
[Exs 3] Correct listing 1.2 step by step. Start from the first diagnostic line, fix the code that is mentioned there, recompile, and so on, until you have a flawless program.
[Exs 4] There is a third difference between the two programs that we didn’t mention yet. Find it.
Takeaway 1.4
A C program should compile cleanly without warnings.
Summary
C is designed to give computers orders. Thereby it mediates between us (the programmers) and computers.
C must be compiled to be executed. The compiler provides the translation between the language that we understand (C) and the specific needs of the particular platform.
C gives a level of abstraction that provides portability. One C program can be used on many different computer architectures.
The C compiler is there to help you. If it warns you about something in your program, listen to it.
Chapter 2. The principal structure of a program
This chapter covers
C grammar
Declaring identifiers
Defining objects
Instructing the compiler with statements
Compared to our little examples in the previous chapter, real programs will be more complicated and contain additional constructs, but their structure will be very similar. Listing 1.1 already has most of the structural elements of a C program.
There are two categories of aspects to consider in a C program: syntactical aspects (how do we specify the program so the compiler understands it?) and semantic aspects (what do we specify so that the program does what we want it to do?). In the following sections, we will introduce the syntactical aspects (grammar) and three different semantic aspects: declarative parts (what things are), definitions of objects (where things are), and statements (what things are supposed to do).
2.1. Grammar
Looking at its overall structure, we can see that a C program is composed of different types of text elements that are assembled in a kind of grammar. These elements are:
Special words: In listing 1.1, we used the following special words:[¹]#include, int, void, double, for, and return. In program text in this book, they will usually be printed in bold face. These special words represent concepts and features that the C language imposes and that cannot be changed.
¹ In C jargon, these are directivesC, keywordsC, and reservedC identifiers.
PunctuationC: C uses several types of punctuation to structure the program text.
There are five kinds of brackets: { ... }, ( ... ), [ ... ], /* ... */, and < ... >. Brackets group certain parts of the program together and should always come in pairs. Fortunately, the < ... > brackets are rare in C and are only used as shown in our example, on the same logical line of text. The other four are not limited to a single line; their contents might span several lines, as they did when we used printf earlier.
There are two different separators or terminators: comma and semicolon. When we used printf, we saw that commas separated the four arguments of that function; and on line 12 we saw that a comma also can follow the last element of a list of elements.
getting-started.c
12 [3] = .00007,
One of the difficulties for newcomers in C is that the same punctuation characters are used to express different concepts. For example, the pairs {} and [] are each used for three different purposes in listing 1.1.[[Exs 1]]
[Exs 1] Find these different uses of these two sorts of brackets.
Takeaway 2.1
Punctuation characters can be used with several different meanings.
CommentsC: The construct /* ... */ that we saw earlier tells the compiler that everything inside it is a comment; see, for example, line 5:
getting-started.c
5/* The main thing that this program does. */
Comments are ignored by the compiler. It is the perfect place to explain and document your code. Such in-place documentation can (and should) greatly improve the readability and comprehensibility of your code. Another form of comment is the so-called C++-style comment, as on line 15. These are marked with //. C++-style comments extend from the // to the end of the line.
LiteralsC: Our program contains several items that refer to fixed values that are part of the program: 0, 1, 3, 4, 5, 9.0, 2.9, 3.E+25, .00007, and element %zu is %g, \tits square is %g\n
. These are called literalsC.
IdentifiersC: These are names
that we (or the C standard) give to certain entities in the program. Here we have A, i, main, printf, size_t, and EXIT_SUCCESS. Identifiers can play different roles in a program. Among other things, they may refer to
Data objectsC (such as A and i). These are also referred to as variablesC.
TypeC aliases, such as size_t, that specify the sort
of a new object, here of i. Observe the trailing _t in the name. This naming convention is used by the C standard to remind you that the identifier refers to a type.
Functions, such as main and printf.
Constants, such as EXIT_SUCCESS.
FunctionsC: Two of the identifiers refer to functions: main and printf. As we have already seen, printf is used by the program to produce some output. The function main in turn is definedC: that is, its declarationCintmain(void) is followed by a blockC enclosed in { ... } that describes what that function is supposed to do. In our example, this function definitionC goes from line 6 to 24. main has a special role in C programs, as we will encounter: it must always be present, since it is the starting point of the program’s execution.
OperatorsC: Of the numerous C operators, our program only uses a few:
= for initializationC and assignmentC,
< for comparison,
++ to increment a variable (to increase its value by 1), and
* to multiply two values.
Just as in natural languages, the lexical elements and the grammar of C programs that we have seen here have to be distinguished from the actual meaning these constructs convey. In contrast to natural languages, though, this meaning is rigidly specified and usually leaves no room for ambiguity. In the following sections, we will dig into the three main semantic categories that C distinguishes: declarations, definitions, and statements.
2.2. Declarations
Before we may use a particular identifier in a program, we have to give the compiler a declarationC that specifies what that identifier is supposed to represent. This is where identifiers differ from keywords: keywords are predefined by the language and must not be declared or redefined.
Takeaway 2.2
All identifiers in a program have to be declared.
Three of the identifiers we use are effectively declared in our program: main, A, and i. Later on, we will see where the other identifiers (printf, size_t, and EXIT_SUCCESS) come from. We already mentioned the declaration of the main function. All three declarations, in isolation as declarations only,
look like this:
int main(void); double A[5]; size_t i;
These three follow a pattern. Each has an identifier (main, A, or i) and a specification of certain properties that are associated with that identifier:
i is of typeCsize_t.
main is additionally followed by parentheses, ( ... ), and thus declares a function of type int.
A is followed by brackets, [ ... ], and thus declares an arrayC. An array is an aggregate of several items of the same type; here it consists of 5 items of type double. These 5 items are ordered and can be referred to by numbers, called indicesC, from 0 to 4.
Each of these declarations starts with a typeC, here int, double, and size_t. We will see later what that represents. For the moment, it is sufficient to know that this specifies that all three identifiers, when used in the context of a statement, will act as some sort of numbers.
The declarations of i and A declare variablesC, which are named items that allow us to store valuesC. They are best visualized as a kind of box that may contain a something
of a particular type:
Conceptually, it is important to distinguish the box itself (the object), the specification (its type), the box contents (its value), and the name or label that is written on the box (the identifier). In such diagrams, we put ?? if we don’t know the actual value of an item.
For the other three identifiers, printf, size_t, and EXIT_SUCCESS, we don’t see any declaration. In fact, they are predeclared identifiers, but as we saw when we tried to compile listing 1.2, the information about these identifiers doesn’t come out of nowhere. We have to tell the compiler where it can obtain information about them. This is done right at the start of the program, in lines 2 and 3: printf is provided by stdio.h, whereas size_t and EXIT_SUCCESS come from stdlib.h. The real declarations of these identifiers are specified in .h files with these names somewhere on your computer. They could be something like:
int printf(char const format[static 1], ...); typedef unsigned long size_t; #define EXIT_SUCCESS 0
Because the specifics of these predeclared features are of minor importance, this information is normally hidden from you in these include filesC or header filesC. If you need to know their semantics, it is usually a bad idea to look them up in the corresponding files, as these tend to be barely readable. Instead, search in the documentation that comes with your platform. For the brave, I always recommend a look into the current C standard, as that is where they all come from. For the less courageous, the following commands may help:
Terminal
0 > apropos printf 1 > man printf 2 > man 3 printf
A declaration only describes a feature but does not create it, so repeating a declaration does not do much harm but adds redundancy.
Takeaway 2.3
Identifiers may have several consistent declarations.
Clearly, it would become really confusing (for us or the compiler) if there were several contradicting declarations for the same identifier in the same part of the program, so generally this is not allowed. C is quite specific about what the same part of the program
is supposed to mean: the scopeC is a part of the program where an identifier is visibleC.
Takeaway 2.4
Declarations are bound to the scope in which they appear.
These scopes of identifiers are unambiguously described by the grammar. In listing 1.1, we have declarations in different scopes:
A is visible inside the definition of main, starting at its declaration on line 8 and ending at the closing } on line 24 of the innermost { ... } block that contains that declaration.
i has more restricted visibility. It is bound to the for construct in which it is declared. Its visibility reaches from that declaration on line 16 to the end of the { ... } block that is associated with the for on line 21.
main is not enclosed in a { ... } block, so it is visible from its declaration onward until the end of the file.
In a slight abuse of terminology, the first two types of scope are called block scopeC, because the scope is limited by a blockC of matching { ... }. The third type, as used for main, which is not inside a { ... } pair, is called file scopeC. Identifiers in file scope are often referred to as globals.
2.3. Definitions
Generally, declarations only specify the kind of object an identifier refers to, not what the concrete value of an identifier is, nor where the object it refers to can be found. This important role is filled by a definitionC.
Takeaway 2.5
Declarations specify identifiers, whereas definitions specify objects.
We will later see that things are a little more complicated in real life, but for now we can make the simplification that we will always initialize our variables. An initialization is a grammatical construct that augments a declaration and provides an initial value for the object. For instance,
size_t i = 0;
is a declaration of i such that the initial value is 0.
In C, such a declaration with an initializer also defines the object with the corresponding name: that is, it instructs the compiler to provide storage in which the value of the variable can be stored.
Takeaway 2.6
An object is defined at the same time it is initialized.
Our box visualization can now be completed with a value, 0 in this example:
A is a bit more complex because it has several components:
getting-started.c
8 double A
[5] = {
9 [0] = 9.0, 10 [1] = 2.9, 11 [4] = 3.E+25, 12 [3] = .00007, 13 };
This initializes the 5 items in A to the values 9.0, 2.9, 0.0, 0.00007, and 3.0E+25, in that order:
The form of an initializer that we see here is called designatedC: a pair of brackets with an integer designate which item of the array is initialized with the corresponding value. For example, [4] = 3.E+25 sets the last item of the array A to the value 3.E+25. As a special rule, any position that is not listed in the initializer is set to 0. In our example, the missing [2] is filled with 0.0.[²]
² We will see later how these number literals with dots (.) and exponents (E+25) work.
Takeaway 2.7
Missing elements in initializers default to 0.
You might have noticed that array positions, indicesC, do not start with 1 for the first element, but with 0. Think of an array position as the distance of the corresponding array element from the start of the array.
Takeaway 2.8
For an array with n elements, the first element has index 0, and the last has index n-1.
For a function, we have a definition (as opposed to only a declaration) if its declaration is followed by braces { ... } containing the code of the function:
int main(void
) {
...
}
In our examples so far, we have seen names for two different features: objectsC, i and A, and functionsC, main and printf. In contrast to object or function declarations, where several are allowed for the same identifier, definitions of objects or functions must be unique. That is, for a C program to be operational, any object or function that is used must have a definition (otherwise the execution would not know where to look for them), and there must be no more than one definition (otherwise the execution could become inconsistent).
Takeaway 2.9
Each object or function must have exactly one definition.
2.4. Statements
The second part of the main function consists primarily of statements. Statements are instructions that tell the compiler what to do with identifiers that have been declared so far. We have
getting-started.c
16 for (size_t i = 0; i < 5; ++i) { 17 printf(element %zu is %g, \tits square is %g\n
, 18 i, 19 A[i], 20 A[i]*A[i]); 21 } 22 23 return EXIT_SUCCESS;
We have already discussed the lines that correspond to the call to printf. There are also other types of statements: for and return statements, and an increment operation, indicated by the operatorC ++. In the following section, we will go a bit into the details of three categories of statements: iterations (do something several times), function calls (delegate execution somewhere else), and function returns (resume execution from where a function was called).
2.4.1. Iteration
The for statement tells the compiler that the program should execute the printf line a number of times. This is the simplest form of domain iterationC that C has to offer. It has four different parts.
The code that is to be repeated is called the loop bodyC: it is the { ... } block that follows the for ( ... ). The other three parts are those inside the ( ... ) part, divided by semicolons:
The declaration, definition, and initialization of the loop variableCi, which we already discussed. This initialization is executed once before any of the rest of the entire for statement.
A loop conditionC, i< 5 specifies how long the for iteration should continue. This tells the compiler to continue iterating as long as i is strictly less than 5. The loop condition is checked before each execution of the loop body.
Another statement, ++i, is executed after each iteration. In this case, it increases the value of i by 1 each time.
If we put all of these together, we ask the program to perform the part in the block five times, setting the value of i to 0, 1, 2, 3, and 4, respectively, in each iteration. The fact that we can identify each iteration with a specific value for i makes this an iteration over the domainC 0, . . ., 4. There is more than one way to do this in C, but for is the easiest, cleanest, and best tool for the task.
Takeaway 2.10
Domain iterations should be coded with a for statement.
A for statement can be written in several ways other than what we just saw. Often, people place the definition of the loop variable somewhere before the for or even reuse the same variable for several loops. Don’t do that: to help an occasional reader and the compiler understand your code, it is important to know that this variable has the special meaning of an iteration counter for that given for loop.
Takeaway 2.11
The loop variable should be defined in the initial part of a for.
2.4.2. Function calls
Function calls are special statements that suspend the execution of the current function (at the beginning, this is usually main) and then hand over control to the named function. In our example
getting-started.c
17 printf(element %zu is %g, \tits square is %g\n
, 18 i, 19 A[i], 20 A[i]*A[i]);
the called function is printf. A function call usually provides more than just the name of the function, but also arguments. Here, these are the long chain of characters, i, A[i], and A[i]*A[i]. The values of these arguments are passed over to the function. In this case, these values are the information that is printed by printf. The emphasis here is on value
: although i is an argument, printf will never be able to change i itself. Such a mechanism is called call by value. Other programming languages also have call by reference, a mechanism where the called function can change the value of a variable. C does not implement pass by reference, but it has another mechanism to pass the control of a variable to another function: by taking addresses and transmitting pointers. We will see these mechanism much later.
2.4.3. Function return
The last statement in main is a return. It tells the main function to return to the statement that it was called from once it’s done. Here, since main has int in its declaration, a return must send back a value of type int to the calling statement. In this case, that value is EXIT_SUCCESS.
Even though we can’t see its definition, the printf function must contain a similar return statement. At the point where we call the function on line 17, execution of the statements in main is temporarily suspended. Execution continues in the printf function until a return is encountered. After the return from printf, execution of the statements in main continues from where it stopped.
Figure 2.1 shows a schematic view of the execution of our little program: its control flow. First, a process-startup routine (on the left) that is provided by our platform calls the user-provided function main (middle). That, in turn, calls printf, a function that is part of the C libraryC, on the right. Once a return is encountered there, control returns back to main; and when we reach the return in main, it passes back to the startup routine. The latter transfer of control, from a programmer’s point of view, is the end of the program’s execution.
Figure 2.1. Execution of a small program
Summary
C distinguishes the lexical structure (the punctuators, identifiers, and numbers), the grammatical structure (syntax), and the semantics (meaning) of programs.
All identifiers (names) must be declared such that we know the properties of the concept they represent.
All objects (things that we deal with) and functions (methods that we use to deal with things) must be defined; that is, we must specify how and where they come to be.
Statements indicate how things are going to be done: iterations (for) repeat variations of certain tasks, functions call (printf(...)) delegate a task to a function, and function returns (returnsomething;) go back where we came from.
Level 1. Acquaintance
Our mascot for this level, the common raven, is a very sociable corvid and known for its problem-solving capacity. Ravens organize in teams and have been observed playing even as adults.
This level will acquaint you with the C programming language: that is, it will provide you with enough knowledge to write and use good C programs. Good
here refers to a modern understanding of the language, avoiding most of the pitfalls of early dialects of C, and offering you some constructs that were not present before and that are portable across the vast majority of modern computer architectures, from your cell phone to a mainframe computer. Having worked through these chapters, you should be able to write short code for everyday needs: not extremely sophisticated, but useful and portable.
Buckle up
In many ways, C is a permissive language; programmers are allowed to shoot themselves in the foot or other body parts if they choose to, and C will make no effort to stop them. Therefore, just for the moment, we will introduce some restrictions. We’ll try to avoid handing out guns in this level, and place the key to the gun safe out of your reach for the moment, marking its location with big and visible exclamation marks.
The most dangerous constructs in C are the so-called castsC, so we’ll skip them at this level. However, there are many other pitfalls that are less easy to avoid. We will approach some of them in a way that might look unfamiliar to you, in particular if you learned your C basics in the last millennium or if you were introduced to C on a platform that wasn’t upgraded to current ISO C for years.
Experienced C programmers: If you already have some experience with C programming, what follows may take some getting used to or even provoke allergic reactions. If you happen to break out in spots when you read some of the code here, take a deep breath and try to relax, but please do not skip these pages.
Inexperienced C programmers: If you are not an experienced C programmer, much of the following discussion may be a bit over your head: for example, we may use terminology that you have not yet even heard of. If so, this is a digression for you, and you may skip to the start of chapter 3 and come back later when you feel a bit more comfortable. But be sure to do so before the end of this level.
Some of getting used to
our approach on this level may concern the emphasis and ordering in which we present the material:
We will focus primarily on the unsignedC versions of integer types.
We will introduce pointers in steps: first, in disguise as parameters to functions (section 6.1.4), then with their state (being valid or not, section 6.2), and then, on the next level (chapter 11), using their entire potential.
We will focus on the use of arrays whenever possible, instead.
You might also be surprised by some style considerations that we will discuss in the following points. On the next level, we will dedicate an entire chapter (chapter 9) to these questions, so please be patient and accept them for the moment as they are.
We bind type modifiers and qualifiers to the left. We want to separate identifiers visually from their type. So we will typically write things as
char* name;
where char* is the type and name is the identifier. We also apply the left-binding rule to qualifiers and write
char const* constpath_name;
Here the first const qualifies the char to its left, the * makes it to a pointer, and the second const again qualifies what is to its left.
We do not use continued declarations. They obfuscate the bindings of type declarators. For example:
unsigned const*consta, b;
Here, b has type unsigned const: that is, the first const goes to the type, and the second const only goes to the declaration of a. Such rules are highly confusing, and you have more important things to learn.
We use array notation for pointer parameters. We do so wherever these assume that the pointer can’t be null. Examples:
/* These emphasize that the arguments cannot be null. */size_tstrlen(char conststring[static 1]);