Mike’s Web Site & Blog

No Subtitle Exists

Programming in C: 102

This article is part of the Learning to Program in C series.

In the previous article, you installed and tested a C compiler. Now you’ll take a look at what some of the (very simple) things you can do with it.

What can you do with a C compiler right now? Making the assumption that you do not know C, and have no knowledge about the library functions which are available in it… well, not much more than copying and pasting, and a little bit of reading.

But that’s OK, because you’ll pick it up as you go along.

A Not-Too-Simple Example Program

Let’s take a look at a basic C program that expects to be run in a POSIX® environment, so that we can break it down into its components.

/* Region 1 */
#include <stdio.h>
#include <string.h>

#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
/* End Region 1 */

/* Region 2 */
#define SUCCESS          0
#define ERROR_OPEN_FILE  1
/* End Region 2 */

/* Region 3 */
main(int argc, char *argv[]) {
    int fd = open("file.txt", O_RDWR | O_CREAT, 0600);
    if(fd < 0) {
        fprintf(stderr, "error opening file: %s\n", strerror(errno));
        return ERROR_OPEN_FILE;

    int rc = write(fd, "Hi!\r\n", 5);
    if(rc < 0) {
        fprintf(stderr, "error writing file: %s\n", strerror(errno));
        return ERROR_WRITE_FILE;

    return SUCCESS;
/* End Region 3 */


The first thing that should be noticed is that I’ve used comments to break the program apart into three distinct regions. In C, there are two styles of comment: block comments and single-line comments.

A block comment starts with /* and ends with the very first */ encountered after the block comment start. This means that block comments may not be nested. This type of comment has been in the C language for approximately forever, and is safe to use even in source code which may be processed by older compilers.

A single-line comment starts with // and terminates at the end of the line.

Preprocessor Directives

After comments, the next note-worthy things are the preprocessor directives; those are the lines that start with the # character.

There are two such directives shown here: #include and #define.

The #include does exactly what the name implies: it includes an arbitrary file, which is traditionally expected to be a header file. The directive has two forms, a global form (e.g., <stdio.h>) and a local form ("file.h"). The global form is used for headers which are provided by the system, and the local form is used for headers which are provided within the current source tree.

There is—by convention only—a semantic difference between a normal C source code file and a C include file. While the C language itself does not distinguish between the two, most programmers themselves do. An include file should be able to be included multiple times without error, and it should device an API, but not the implementation. There are some exceptions, as with any rule (including function-like macros defined for convenience).

The #define directive also does exactly what is implied by its name: it defines a preprocessor value. In the above program, this means that any time the word SUCCESS appears within the program, the C preprocessor will replace it with the number 0. The same goes for the values ERROR_OPEN_FILE and ERROR_WRITE_FILE, which are defined to the values 1 and 2, respectively.


In C, code is provided by the programmer in the form of functions. A function is a relatively simple concept: it is a named object which contains a sequence of instructions. In the program shown above, in Region 3, is the source code for a function called main. The main function is the first user-provided function which is called in a standard C program.

In the program shown above, the main function performs the following tasks:

  • It opens a file named file.txt, by calling the open(2) function, which is specified by POSIX®.
  • It checks to see if the call to open(2) failed. Failure is indicated through a negative return value. If failure is detected, the program is halted with an error message. The error message is sent to the standard error stream using the fprintf(3) function, and the strerror(3) function is used to obtain a meaningful description of the error encountered by the call to open(2).
  • It writes the string “Hi!”, followed by the ASCII CR and LF characters, using the POSIX®-specified write(2) function. Together, the CR+LF pair is known as end-of-line (EOL) marker.
  • It checks to see if the call to write(2) failed. As with the call to open(2), failure is indicated through a negative return value. The program is halted with an error message if write(2) failed. The error message is sent to the standard error stream using the fprintf(3) function, and the strerror(3) function is used to obtain a meaningful description of the error encountered by the call to write(2).
  • If the program makes it through everything above without returning an error code, it returns the value indicating success.

Function Prototypes

A function has a signature, which provides information on the function’s return value and arguments. In the program above, there are a grand total of 5 functions referenced: open(2), close(2), write(2), printf(3), and strerror(3). Each one of those functions takes arguments and returns a value to its caller. The entrypoint function, main, does the same thing.

The return value, name, and function parameter list together form the function prototype. For the main function, the function prototype is:

int main(int argc, char *argv[]);

This says that main is a function which returns a signed integer and accepts two parameters: an integer and a pointer to an array of “strings.” We will cover what a “C string” is in the next article, since it is a construct that many people have difficulty with when starting out. For now, just take it for granted that a string literal is a “string”.

On some systems, such as Linux®, the main function prototype is actually as follows:

int main(int argc, char *argv[]), char *envp[]);

The third argument provides access to the program environment, which is a set of key-value pairs which is inherited from the system. For now, these are unimportant—though environment variables can be a potentially very important (and dangerous!) source of configuration data.

Local Variables

In the very first line of the main function shown above, there are three very important things: a variable declaration, an assignment to that variable, and a function call. For right now, we’re concerned with the variable declaration.

In C, you must declare your variables before you can use them. When you declare a variable, you are telling the compiler what type of data the variable is going to hold. You are also telling the compiler what the name of the variable is.

In the main function shown above, there are two variables. One is named fd and is of type int. You should be able to find the name and type of the second variable now.

Assignment to Variables

If you’ve ever done any math, this is easy. Any expression of the form VAR = (expression); is a variable assignment; the value on the right side of the equal sign is assigned to the variable named on the left side of the equal sign. It is just that simple.


In two places in the demonstration program shown above, there are lines with the if statement—a conditional (or flow control) statement. Inside the parenthesis after the word if is an expression which is deemed to be true if it evaluates to a non-zero value, and true if it evaluates to zero.

For example, the statement if(fd < 0) { A; } else { B; } says to run the code in A if the value held in fd is less than zero, otherwise run the code in B.

Returning a Value

In C, functions which are declared to return something other than void are allowed to (actually: must) return a value. This requires the use of the return keyword. The result of the expression given to the return keyword must be of the same type as the function’s declared return value.

In other words, if the function is declared to return an int, it must return an int and not a char * or a double.

What now?

As of this moment in time—although you might not feel like it just yet—you have enough information to write some extremely simple C programs. If you are feeling adventuresome, you might just try your hand at it. Don’t worry if you aren’t, though—you aren’t expected to be yet.

If you feel up to it, you can start practicing by writing some trivial programs. If not, don’t worry—problems will be given to work on in the next article.

What’s next?

In the next article, we’ll cover a few more of the basics that we didn’t get to here: data types and pointers. Perhaps more than anything else in C, these two concepts combined with the function are what give the programmer power. Also, several functions from the C standard library will be introduced.

After reading the next part you should be ready to write some simple—yet useful—programs in C.


Thank you for stopping by!

If you liked this article you can give a tip or become a patron. It helps pay my bills and keep me writing.