hello friends! new(ish)!
C
C is a programming language designed for application and systems programming. It was created by the late master himself, Dennis Ritchie, so that he could create Unix. C is standardized by ISO and is currently at the C11 standard.
Qualities
C is a very simple language, whose parts can combine to form very complex code structures.
Language
C is an imperative, procedural, structured language. This means:
- Code is a sequence of statements that change program state
- Statements may be organized in control structures like
if
,while
,for
- Code may be organized as functions, which may be called from elsewhere
Type system
C is statically but weakly typed. This means:
- Types must be declared
- Values can be loosely converted between types regardless of compatibility
Examples
The examples provided here can be saved to a file and compiled and run using a C compiler such as gcc
or clang
using the following command:
$ gcc -o example example.c
Hello World!
Let's start with a simple, traditional "Hello, world!" program. It will be dissected and discussed at length.
#include <stdio.h> int main(void) { printf("Hello, world!\n"); return 0; }
The first line in this code is an include statement.
The include statement is a C pre-processor directive which tells the compiler to look for the given header, in our case stdio.h
. The compiler looks for the header in pre-configured system directories, such as /usr/include
if you use a Unix-like operating system. If it is found, it substitutes the include statements for the contents of the header. Basically, it is copy-pasting the contents of the header into the place the include statement was in your source file.
A header file contains declarations of structures and functions. Including a header is how libraries are used in C, even the standard library. If the header file is not included, the compiler will not recognize the function when it comes across it.
Pre-processing happens before compilation proper and is essentially text manipulation. There are many pre-processor commands and they have a multitude of uses. The result of pre-processing a C source file is called a translation unit: it is what ultimately gets compiled, and will most likely contain a lot more text than the source file itself.
Variables
Variables represent the value stored in a memory location. They hold the data you store in them, and from then on you can refer to that data by name.
Variables always have a type that determines how the data is encoded in terms of bits. You can convert variables from one type to another in a process known as type casting. This process alters how the data is interpreted at the bit level and can produce unexpected or undesirable results due to incompatibilities between types.
C has several built-in types. One of them is int
, one of several integer types. These integers are encoded as a sequence of bits. Precisely how the exact sequence of bits is interpreted by the processor depends on the endianness of the platform. Integers can be signed or unsigned: this affects how the most significant bit (MSB) is interpreted. If it is signed, the MSB is used to encode the sign; if it is unsigned, the MSB is just another digit.
#include <stdio.h> int main(void) { // Allocate automatic storage for an integer // Assign it the value of 9001 // Bind it to the name power_level int power_level = 9001; // %d serves as a placeholder // It means "put the first argument here" // It also tells printf that the argument is a signed integer printf("%d\n", power_level); }
Control structures
C is a structured programming language, which means it has formal, high-level syntactic constructs for controlling program flow. A step up the likes of assembly and BASIC, where all you have is goto
. Speaking of the devil: that keyword has its uses, but that's for another time.
#include <stdio.h> int main(void) { printf("Calculating powerlevel...\n"); int power_level = 1000; printf("Hmm... %d. Not impressive.\n", power_level); // Executes the following block while the condition is true // 1 is always true, so it is an infinite loop while (1) { // Increments power_level by one ++power_level; // Is power_level divisible by 2000? if (power_level % 2000 == 0) { // It is, so print it printf("%d ...\n", power_level); } // Is power_level higher than 9000? if (power_level > 9000) { // It is, so get surprised! printf("It's over nine thousand!!\n"); // Breaks the while loop, letting the program finish break; } } return 0; }
Functions
Functions are a way to group a block code that provides some functionality under a name. This way, it can be called whenever we need it. In the example, a function's been present all this time: main
is a function, and our example code are the code blocks that are executed when it is called. The main
function is called by the operating system when it spawns your program's process.
Functions can return values and take arguments. The main
function takes no arguments, as evidenced by the void
between the parentheses, and it returns an integer value back to the operating system; it is the program's exit status. How the operating system interprets that value depends.
#include <stdbool.h> #include <stdio.h> /** * Define a function that returns whether the * given power level is over 9000 */ bool is_over_nine_thousand(int power_level) { return power_level > 9000; } int main(void) { printf("Calculating powerlevel...\n"); int power_level = 1000; printf("Hmm... %d. Not impressive.\n", power_level); // true is actually 1 while (true) { ++power_level; if (power_level % 2000 == 0) { printf("%d ...\n", power_level); } // Call the function passing power_level as argument // Use its result as argument to the if control structure if (is_over_nine_thousand(power_level)) { printf("It's over nine thousand!!\n"); break; } } return 0; }
Pointers
Pointers are a type of variable whose value represents the location of another variable in memory. They provide an indirect way to refer to variables; with pointers, you can refer to the variable itself. Through the pointer, you can read from and write to the variable's contents even if the variable doesn't really exist in the current scope.
Pointer types are denoted by appending an asterisk to the type being pointed to. So, the type of a pointer to an int
would be noted as int *
.
The pointer itself is a variable, and you can create another variable to point to the pointer if you wish. If the type of a pointer to an int
is int *
, the type of a pointer to an int *
is int **
.
#include <stdbool.h> #include <stdio.h> bool is_over_nine_thousand(int power_level) { return power_level > 9000; } // Define a function that increments the power level void increment_power_level(int * power_level) { // "Follows" the pointer, getting to the variable it points to ++(*power_level); } int main(void) { printf("Calculating powerlevel...\n"); int power_level = 1000; printf("Hmm... %d. Not impressive.\n", power_level); while (true) { // Unary & is the "address-of" operator // Returns a pointer to the variable, which is passed to the function // The function is able to operate on our variable directly through the pointer increment_power_level(&power_level); if (power_level % 2000 == 0) { printf("%d ...\n", power_level); } if (is_over_nine_thousand(power_level)) { printf("It's over nine thousand!!\n"); break; } } return 0; }
Remember, variables hold a value. If you pass variables around, you are copying that value into another variable: the argument of a function. If you pass a pointer to a variable, it's as if you were passing the variable itself to the function. It allows you to read and modify it remotely. This is useful if you have a variable that holds a ton of data, like the pixels of an image. Making copies of that simply won't do. The solution is to pass a pointer to that memory.
Finally, pointers themselves are variables. You can read and modify pointers remotely as well. They, too, are copied when they are passed to functions or assigned to other pointers. However, the size of a pointer is always constant and they are small enough for it to not matter.
Many other languages pretend to hide pointers from you. However, variable names in those languages work very much like pointers do in C. What they really do is hide variables from you. You never actually get at the memory itself; that memory stays hidden away, protected in the bowels of the virtual machine that implements the language.
Even Java has a NullPointerException
, and it manifests itself in other languages in various forms. Nobody will ever, ever be free of that error, because it's just how abstraction works: through levels of indirection.