C Programming in 2021

by Ray Dall

PREFACE

I have just begun (19MAR2021) to rewrite this course as much of it was outdated.
C is just as relevant today as it was the day it was created.
My writing style has grown, and this is likely to be less of a dry read.
I am also more familiar with C than I was 20 years ago when I wrote the original course.
Hope you enjoy this new version.

COURSE REQUIREMENTS

Every course you take has some kind of requirements. English Literature courses require that you know the alphabet, can read, preferably in English. Algebra requires that you have some basic general math skills. Auto Mechanics requires that you have access to tools, and something with an engine.

Before you can actually write any code and turn it into a working program, there are a few things you are going to need. Understanding that many of the people who take my course may not have disposable incomes, I try to keep things as inexpensive as possible.

In this course, you will need a computer. I teach C as it applies to its native operating system - UNIX. At bare minimum, you need a UNIX/Linux based computer. If you do normally use Windows, don't be afraid. I'll try to make this as painless as possible.

If you want to get the most out of the course, invest in an inexpensive,
Raspberry Pi computer. Raspberry Pi Computer

For $85, you get a pocket sized, fully functional quad core 64 bit computer, that can handle DUAL HDMI Monitors! It will plug right into your TV set. It is actually nice to write code from your easy chair on a 65 inch monitor. It is ready to go and comes with all the software you'll ever need to write code, and to be in control of the "Internet of Things". Check out these specs!

Finally, every program we write in this course will be 100% tested to work on that computer.

I'm not implying that the C programs will not work under Windows, Mac, or another version of Linux/Unix, merely that I haven't tested them on those units, and can not guarantee the results. C is a cross platform language and should work equally well on any system, but as they say - when you make something idiot proof - they make better idiots. So it goes with computer programming. I can write a program today that works great on a particular machine, and one small change in the Operating System can render the program inoperable. This, of course, is true of any programming language, not just C.

In addition to the computer, before you can actually write any code and turn it into a working program, you will also need a C compiler and a text editor of some kind. Unix computers come with a totally free compiler built in. Let's face it - UNIX was at least partly written in C!

In windows, of course, you'll have to BUY a C compiler and/or Software Developement Suite, which will cost you HUNDREDS of dollars.

Buy Microsoft Visual Studio for only $484.99


If you don't want to spend ANY money, you can simply download a copy of Linux (for free) and use it on your existing computer. I personally use Debian, which is one of the most venerable and well supported distributions, and is the grandfather of Ubuntu. Most of Linux was WRITTEN in C, and Linux comes with all the software you need to write, compile and debug the C language. I'm not saying you have to wipe your hard drive and get rid of Windows completely (why not?, I did!). There are other ways. Nowadays you can get what is called a "Live" distribution of Linux that you can run from a DVD, CD, or flash drive. Just plug it in and boot your computer. Use it for the course, then unplug it and reboot back into Windows when you are done. THIS course does not cover how to do that.

Advantage: Most programmers worth their salt use Linux nowadays, and if you DON'T familiarize yourself with the operating system, you may find it difficult to get a good paying job after college! (Employers tend to pay $10K-$25K more a year for people with Linux background). So now you have TWO reasons for at least giving Linux a try.

Why C?

C, while a bit complex, is one of the oldest, fastest, and most reliable languages there is. Being VERY close to the computer's original machine language, with the ability to output in Assembly (executable code) without the need for an interpreter (which slows the computer down at every command) - C is fast... Lightning fast.

C is also the mother of many other languages. Because of that, if you know C, you have a great head start at learning C++, C#, Java, and even Golang! Learning C will teach you the necessary disciplines, and in many cases, even the code necessary to work with other languages. Point of fact - if you can code in C, you can code in C++... because a C++ compiler will compile a C program just fine.

It is also cross-platform, which means that if you properly write a program in C on a Linux machine, it will work equally well on a Mac, Windows, Cell phone, or even some car's central processing unit!

Anything you can do with a computer, you can do with C. Being a compiled language, it is not only fast, but secure. Languages like Python may be easier to learn, but it leaves the source code on the machine, which of course can be read (and hacked) by anyone. Compiled languages need only leave an executable binary file, when can not be easily read, nor hacked. Properly written C code is very stable, very fast, very secure and very adaptable. The question then shouldn't be why C, but rather, why not?

History

Before we get into the bits and bytes of the language, lets get some historical perspective as to where the language came from, and where it is heading.

C is likely one of the most influential programming languages in the world. It isn't the first, nor will it be the last, but it has left a lasting impression, and even though it was one of the earliest adopted languages, it is still used on a regular basis on everything from toys to spacecraft.

Shortly after graduation from Berkley, Electrical Engineer Ken Thompson landed a job with AT&T's Bell Laboratories. One of his first assignments there was to work with a multi-company consortium to build an operating system that could work on many different computer platforms. That system was called MULTICS. At the time, computers were mostly programmed in Assembly language, and a program written for one computer could not be easily ported to another. Multics was supposed to fix that. Multics, however, wound up being a bust, and Ken Thompson went back to work at Bell Laboratories home office. Many lessons were learned, though, in the process of writing MULTICS. One, was the language BCPL (Basic Combined Programming Language...B for short). Another great outcome of the project was a Space Travel game program written for testing the machine.

Around 1968, Ken, having recently displaced from the Multics project, was looking for something to do. (Engineers don't idle well). He requested a new PDP-11 to continue working on the MULTICS project solo, but Bell Labs was loath to buy a brand new computer to continue a project they had just aborted. Wandering the halls, Ken located an older PDP-7 in a storage closet. He requested to use it and the request was granted. Now a PDP-7 wasn't a full blown computer. It was merely a smart terminal. (A keyboard and monitor with just enough processor and memory to act as a buffer to eliminate lag and crashes between itself and the mainframe). Not being (considered) a full blown computer, he had to really conserve memory space and crop his code down the the bare necessities.

He wrote a stripped down version of BCPL. He also had to write a very small operating system to work in the PDP-7, which he called UNICS (a play on MULTICS). We now call it UNIX. During this same time, a co-worker by the name of Dennis Ritchie started writing a language who's core features were derrived from B, but also incorporating many ideas from other colleagues. This new custom programming language, they called "C". C was written in assembly language, as was UNIX originally, but later UNIX was re-written in C so that it could be ported easily to other computers. B being incapable of handling byte addressability in the PDP-11, C took over the job. The developement of C and the UNIX operating system are closely intertwined. Most of its kernel was written in C, as well as Linux, BSD, and several other Unix based operating systems and programs. This was one of the first kernels ever written in a language other than the native Assembly language of the processor, and it allowed cross-platform programming - so that a program written for one computer could be used on another one.

Sometime around 1978, the "The C Programming Language", otherwise known as "K&R" (named after the authors Kernhigan and Ritchie) was written. This became the defacto standard book used in many colleges. While it may be in my opinion, a bit terse and cryptic, and much of the code is either obsoleted, insecure, or just won't run on many computers - tt is without a doubt a very high standard and authority on the language - considering one of the authors was the author of the language itself. That being said, I have personally found that on some machines, some of the programs just don't work and need a little 'tweaking'. Also, there are some commands that were used "back in the day" that are now considered to be taboo, as they create security risks. Long story short, I wouldn't use K&R as my Bible.

In 2000, Herbert Schildt came out with "C/C++ Programmer's Reference". This, while not a proper course on the language, is in my opinion one of the most authoritative quick reference guides for C and C++ programmers, and I DO highly suggest that you get your hands on a copy of it and devour it.

Now that you know who wrote C, when it was written, and the names of a couple of good reference books - the question still remains, "What exactly IS C, and why do I need to learn it?".

Computers don't speak C. Computers speak machine language.... the language of 1's and 0's. This is because internally, the computer is nothing more than a vast array of switches (think on and off). Maching language is a binary language, with only 2 possible characters, a one or a zero. As such a typical computer sees something like the following in its own world:


11010010 11010010 10011011 01101101 11010010 11010010 10011011 01101101
11010010 11101001 10010010 11011011 11010010 11101001 10010010 11011011
10011011 01101101 11010010 11101001 10010010 11011011 11010010 11010010
10011011 01101101 11010010 11010010 11010010 10011011 01101101 11010010
11101001 10010010 11011011 11101001 10010010 11011011 11101111 00100100


Except for a very few people in the world, humans of course, can't read that for crap. We need something more "human-like" to read, and so the invention of "Assembly Language". Assembly language uses commands in the microprocessor itself to speak computer-ese. But assembly language is also a bit on the cryptic side:



push ax
push bx
mov ax,33
mov bx,45
add ax,bx
pop bx
pop ax
jnz next


Assembly is considered a "Low Level" language - in that it is as close to speaking the computer's actual language as most humans can ever achieve. We speak in a "High Level" language - humanese. In order to make things easier on us, we either need a compiler or an interpreter.

Let's take a moment to discuss the difference between a compiler and an interpreter.

Assume for a moment that you are a king. You want to make a treaty with another king, but the two of you don't speak the same language. How then, do you communicate the terms of the alliance between each other? You have 3 options.
1) You can learn the other king's language and speak it yourself.
2) You can hire an interpreter, and speak to the other king through the interpreter. Of course, this takes much time away from your schedule, and the other king's schedule, and every sentence you speak has to be respoken by the interpreter, which slows down the whole communication process.
3) You can hire an ambassadore who speaks the other king's language to meet with the king on your behalf. If the ambassadore you hire has the same basic belief system as you, and understands your wants and desires, he can more quickly communicate with the other king while you go about doing other kingly business. Clearly, the ambassadore route is faster, and more efficient, and so long as your ambassadore is on the same thought process as you are, you can trust that he is going to represent you well.

The interpreter in the above scenereo is identical to an interpreter in an interpreted language. The Ambassadore is more like the compiler. You tell it what you want it to do in your native language, and he changes it into a program the computer's native language.

A compiler is like a converter that compiles (converts) the program we wrote INTO an assembly language program. An interpreter, on the other hand, runs in the background (using up precious memory and speed), interpreting commands from a high level language into a low level language realtime. High level languages like C or Fortran are compiled into Assembly language, and run quickly. High level languages like BASIC and Python are never compiled. Instead, they run with an interpreter program running in the background, and as such - it takes 2 programs to do the job of one (the program you wrote, and the interpreter program), so it uses twice the memory, and processing resources, so it runs twice as slow.

You can see the obvious reason, then, why learning a compiled language like C is much better than learning an interpreted language like Python or BASIC, because compiled languages turn their programs into Assembler code, and run much faster. Python and BASIC may be easier to learn at first, (because you don't have to learn how to fuss with a compiler), but in the end, C is just as easy to write programs in if you learn it, and it simply writes a faster, cleaner, more compact code.

Now on with the C course:



From this point on, I'm going to assume that you are using some form of UNIX based system to write your code on. I'm hoping you took the Raspberry Pi route. You won't be disappointed.

There are 3 basic steps in working with C:


WRITE the code (using some kind of simple text editor).
COMPILE the code (using a compiler).
TEST/RUN the program that is spewed out by the compiler.

In order to write code, you will need a text editor... NOT A WORD PROCESSOR - there is a difference. You will need to use the editor to write your code, then save it out to a standard ascii text file using a ".c" file extension (more on this later). In Linux/Unix you can use ed, vi, vim, emacs, pico, nano or many others.

If you are already familiar with one of the editors I mentioned above, feel free to use it. Otherwise, I suggest you use nano, as I find that to be the easiest for new students to learn. You will want to learn vi or ed at some point, but it isn't necessary and can be an unnecessary learning hurdle when first learning c.

Do NOT use a "Word Processor" that is capable of italics, bold, or other font formatting, as this will mess you up big time. Stick to a simple text editor, not a word processor.

In your text editor, type the following program (DO NOT CUT AND PASTE IT!). Repetition is the key to learning, so if you want to learn this well - type it out. You won't learn it as well if you simply copy what I did.

MOST instructors start you out with a "Hello World" program. I am intentionally going to take you a different route. Open a command line interface (aka: terminal or shell) on your computer and initiate your text editor. If you were smart and got that Raspberry Pi I suggested, simply type:


nano first.c



This will open up your nano text editor allowing you to begin writing code. It should look like this:

(Click for Full Size in another tab)
Instructions for using nano are along the bottom of the page. the carrot symbol "^" stands for the [CONTROL] or [CTRL] button on your keyboard (Usually the lower left hand corner button). So to exit nano, you would simply type [CTRL] X (Control X) and it exits. One oddity peculiar to nano is that instead of using the word save, it uses "Write Out" [CTRL] O, which was not uncommon for software written during that era. They tended to use the word write instead of save.

Writing code in nano is simple. You just start typing. WYSIWYG (What you see is what you get). When you are done you write it out ([CTL O]) and exit ([CTRL] X).

Let's give it a go....

Type the following into nano:


// THIS IS A TEST

/*
There are two kinds of COMMENT statements in C
Single line comments which begin with //
...
and multiple line comments that start with a slash asterisk, and
end with an asterisk slash.
*/

// Comments don't actually DO anything.


Now save the program using write out ([CTRL] O), and exit using [CTRL X]. You have just written your first nano file. To reopen that file, simply type the same thing in you did to create it...


nano first.c



Like it said in the file you wrote, Comments don't actually do anything - they are simply there to keep you, the programmer, on track. They sure help out 5 years later when you are trying to figure out why you wrote a particular bit of code the way you did. Comments = understanding. Now that you know how to use nano... let's actually write a program. Reopen your first.c program ( nano first.c ), and remove all your comments, then write the following code:

// type	name	arguments	endpunc
   int	main	()		{}



Once you have typed this into nano, write it out as "first.c", then exit. You have just WRITTEN your first C program. It doesn't do much... but it does work. Before you can run the program, you have to COMPILE it. Do this by typing:


cc first.c



The "cc" stands for (are you ready for this?) C Compiler. Wow.
If you have made no mistakes, it will simply return to the command prompt with no error messages.

Think of the compiler as the cruelest, meanest, most critical grammar teacher you will ever have. It doesn't give you any credit for doing a good job, and it yells at you for even the slightest punctuation error. With that thought in mind.... if it didn't yell at you - you did fine.

If you now type:

ls [ENTER]


You should see a new file has been created called "a.out"


$> ls 
   a.out	first.c
$> 


So you have WRITTEN your program (first.c), and COMPILED it ... now all you have to do is test/run it.


$> ./a.out 
$> 


"It didn't do anything!?"

I know that is what you are thinking. Fact is, you really didn't tell it to do anything. You wrote a blank program. The purpose of the exercise was to teach you two things:
1) How to write, save, compile, and run a program. So far - you are a glorious success!
2) The necessary parts of a FUNCTION (aka: statement).

Before we go any further, lets lay down a framework of common terms that you'll need to know and understand:
Operator:       A symbol that tells the compiler to perform specific mathematical 
                or logical functions (+ - / * etc.) 

Operand         A value (fixed or variable) acted upon by an operator
                c = a + b;

                a and b are operands 
                +  is the operator.

Expression:     A sequence of operators and operands often yielding a single value.

Statement:      An expression followed by a semicolon.
 
Function:       A program or subroutine made up of at least one, often several statements.

Argument:       A value that is passed to a function


The C programming language is made up of commands called Statements or Functions. A Statement is a directive to the computer to "go do something". A Function is a group of statements that perform a particular task.
Function - change the tire....
Statement - put the wrench on the first lug nut

A function may contain a single statement, or several statements. A function can be a stand alone program all by itself, or there may be multiple functions within a program. All functions have the same basic parts, and if you skip one of them, the compiler will barf at you.

The parts of every function are:
the type,
the identifier,
the arguments,
and the ending punctuation.

Some statements may exclude some of the above.

The TYPE indicates what type of RETURN the function will give (more on this shortly). The identifier, of course, is the name of the function. ALL C programs must have at least one function, and that function must be called main(). The program may have other functions in it as well, but it must always include the main().

// type	identifier	arguments	endpunc
   int	main		()		{}



The arguments are data, either text or numerical, which is fed to the function for the function to act upon. That data can be inserted directly within your code, by the user at time of execution, or by another program. We'll cover this in more detail later.

Finally, the endpunc.... ending punctuation. Think of this as the period at the end of a sentence. Note that in English (your language) you don't have to have a period. You might end a sentence with a question mark (?) or an exclamation point (!). There are two, and only two legal types of endpunc in the C language. The curley braces {} and the semicolon ; ...and each has its own purpose.

If the function contains other functions within it, you use the curley brace. If it is a stand-alone function, it will have a semicolon. Think of the semicolon as the period at the end of the sentence. If you forget it, your grammar teacher doesn't just take points off your paper, they completely fail you. If you forget one endpunc, you fail the whole paper. I told you that the compiler was the meanest grammar teacher in the world!

So let's go back into our program and make some changes.... let's make it actually DO something!

nano first.c

// NAME:	first.c
// DESCRIPTION:	A first C program to teach the parts of a function.
   int	main()		
   {
// type	identifier	arguments	endpunc
      	return		(12)		;
   }




Notice that this time, we added return(12); to the program. The curley braces are not on the same line anymore, but that doesn't matter, so long as you still have an open and close curley brace.

The function main() still has a type, a name, arguments (which are blank), and endpunc (the curley braces).

We have added a "return" statement, which also has an identifier (return), an argument (12), and endpunc (the semicolon).

Note that we don't have a "type" for return. Statements do not necessarily follow the same rules as a function, but often they can.

So what exactly does the return(12); do? I thought you'd never ask!

If you compile and run the program, it "returns" a code (specifically an integer) to the shell. That code is interpreted as a success or failure code. Typically, if it returns a zero (0), it indicates a successful completion of the program. To see whether the program successfully completed or not, type echo $? into the terminal.

In this particular case, instead of it returning what would be the nominal success code (0), we have told it to return a 12. Lets see what happens when we compile and run it...

$>   cc first.c
$>   ./a.out
$>   echo $?
     12
$>   



Because you TOLD it to return 12, it returned a 12 to the shell. When you ask the shell what the response from the last running program was (echo $?) it gives you the return. If you want to test the theory, go back into the program and change the 12 to some other number, and compile and run it again. When you ask for the echo $? response, you will get that number. (hint - if it didn't give you the same number, you probably forgot to compile it with cc ).

// NAME:	first.c
// DESCRIPTION:	A first C program to teach the parts of a function.

   int	main()		
   {
      	return(5);
   }



Compile and run...

$>   cc first.c
$>   ./a.out
$>   echo $?
     5
$>   



Let us take this a step further. If all a C program could do is return whatever number we told it to, it would be kind of useless. We need for it to be able to do other stuff, like print words out, create files, do math, etc. So let's add some simple math stuff, just to prove it can do it, then we'll start to get fancy... Let's write another program:

nano second.c

// NAME:	second.c
// DESCRIPTION:	My SECOND program can do math!

   int	main()		
   {
	int x;
	x=5;
	int y;
	y=10;
	int z;
	z=x+y;	
    	return(z);
   }



Compile and run...

$>   cc second.c
$>   ./a.out
$>   echo $?
     15
$>   



So you see now, that we can not only send data to the shell, but that we can do maths and send the result to the shell. While this is handy, this is NOT normally done. Under normal conditions, return() in a C program should typically return ZERO unless there is some kind of a problem. The return of zero, in most cases, indicates a successful completion of a program. Return of some other number usually is the result of a program failure.

   int	main()		
   {
	return(0);
   }



Now let us examine a more realistic program. One which most schools will use as their first program, but misses some of the fine points you just learned. The "Hello World" program.


Once again, type the following into nano (nano printing.c), then write it out ([CTL] X).
(You should be an old hand at this by now).

/* 
   NAME:	printing.c
   DESCRIPTION:	Teaches about compiler errors, inclusion, header files, and the standard input/output header.
   SYNOPSIS:	./a.out
*/
   int	main()		
   {
	printf("Hello World\n");
	return(0);
   }



Once you have typed this into your text editor and saved it as a .c file, and assuming you've made no mistakes, you have a fully functional C program. One small problem: Computers don't speak C - so you are going to have to compile (convert) it into an Assembly language program. This is done using the C compiler:

cc printing.c 



Note: If you typed this into nano properly, when you compiled it, you should have gotten an error message much like the one below.


$: > cc ./printing.c
./printing.c: In function "main":
./printing.c:6:2: warning: incompatible implicit declaration of built-in function "printf" [enabled by default]
  printf("Hello World\n");



Here is a good point to teach you a couple of acronyms.

     RTFM and RTFC

RTFM is an acronym for:

Read
The
FULL
Manual

RTFC is an acronym for:

Read
The
Frustrated
Compiler

In this particular case, we frustrated our compiler by telling it to do something it wasn't equipped to do. Whenever you do that, the compiler barfs out a message telling you just how inadaquate you are, and usually giving you hints on how you can improve your code.

Let's RTFC and examine EXACTLY what it is telling us:


$: > cc ./printing.c
./printing.c: In function "main":
./printing.c:6:2: warning: incompatible implicit declaration of built-in function "printf" [enabled by default]
  printf("Hello World\n");



We typed:
   $: > cc ./printing.c

In the first line, it tells us which function (main) was at fault. Remember, we can have many functions in a single program.

In the second line, it told us that we "implicitly" declared a function.
   ./printing.c:6:2: warning: incompatible implicit declaration of built-in function "printf" [enabled by default]

You see, up until this point, you haven't written a "printf()" function, nor have you told the compiler where to find one that someone else has written.

Some compilers may be nice enough to even tell you where to find the printf() function, and how to change your code to fix it.


    warning: incompatible implicit declaration of built-in function "printf"
    note: include "<stdio.h>" or provide a declaration of "printf"
    #include <stdio.h> or provide a declaration of "printf"

If your compiler was not so nice as to give you such hints.... that's where the aforementioned acronym RTFM comes in handy.

Every Unix/Linux computer comes with built in MANUALS! (Users manuals, Programmer's Manuals, etc). If you READ the FULL MANUAL you will get all the answers, although they may be a bit terse and/or cryptic. To read a manual for a particular command, just type man.... and the command:

$> man ls



This will give you the "user manual" for the Unix/Linux command "ls" aka list. To exit the manual, simply type q for quit. So if we want to see the programmer's manual for the printf() command, we type:

$> man 3 printf



The 3 behind man tells your computer that you want the command from programmer's manual, rather than the user's manual. Again, you would type "q" to quit the manual.

Note that when you type "man 3 printf" into the shell, it gives you different information than simply typing "man printf". That is because there is both a user command printf and a C function printf().

Specifically what you are looking for, toward the top of the programmer's manual, is this:

SYNOPSIS
       #include <stdio.h>

       int printf(const char *format, ...);




The synopsis section of the manual tells you EXACTLY how to use the command. In this case, it tells you that you can't use it without INCLUDING the standard input/output header ( <stdio.h> ).

Note that it also tells you the proper syntax for the printf statement, including the type (int) identifier (printf) arguments, and endpunc. So let's go back into our program and add the line:

#include <stdio.h>

/* 
   NAME:	printing.c
   DESCRIPTION:	Teaches about compiler errors, inclusion, header files, and the standard input/output header.
   SYNOPSIS:	./a.out
*/

#include <stdio.h>

   int	main()		
   {
	printf("Hello World\n");
	return(0);
   }



This now tells the compiler that when it compiles the program, to use the definition of the printf() function that was already predefined in the standard input/output header ( #include <stdio.h> ).

Now when we write and compile our program, it will compile without the compiler getting frustrated and complaining.
Then you can run the program by typing ./a.out

$> cc printing.c
$> ./a.out
Hello World
$>



The reason why it worked this time and not last time, is because just like you wrote a "printing.c" program that told the shell to print out "Hello World", someone long before you wrote a C program called "stdio.h", and one function in that program is a function called "printf()". Once we included his program as part of our program ( #include <stdio.h> ), it knew what to do with the printf and how it should work.

Interesting to note - if you RTFM (read the FULL manual) for printf(), you'll find that it is an int type (integer).

int printf(const char *format, ...);

This tells us that when it finishes, whether correctly or incorrectly, it returns an integer. If you RTFM, you'll see down at the bottom of the manual, that it has a section called "RETURN VALUE".

In that section it states, "If an output error is encountered, a negative value is returned."

What that tells us is that as long as printf() is successful, it will return a non-negative integer (0,1,2,3,4...) We can use this info later on to act as a type of error checking within a program. For now, it is enough to know that it does have a return, as do most functions in C - including nearly all that you will write.

One final note before we end this chapter. You might be wondering what the "f" is for. Why not just call it print? Simply put, because it prints "formatted" text, not just ascii characters. One of those formatis is the "escape" n ( or /n ) that we put at the end of our hello world. The escape symbol tells printf that the next character is a special character, in in the case of /n, that is the "newline" or carraige return. Another commonly used escape sequence is the /t, or tab. We will cover this in more detail in the next chapter.