Why Learn C? - Mid-level language Allows direct manipulation of data and memory addresses Powerful but still structured, unlike ASM. Not a lot you can't do with it - Linux kernel, NASM etc. - Designed to be compiled Fast. When you write a programme in C initial stage called compilation which checks code and builds executable. No runtime checking to slow things down. - Portable/Universal If you take a little care when coding non-architecture dependant C your code should compile and run on any machine that has a C compiler. Lot more platforms have C compilers than have Java VM. - Universal syntax Many other languages which have additional structural features, eg. C++/Java use a variation on the C syntax. If you have a knowledge of C very easy to move onto one of these object oriented languages. - Nice C is nice. The syntax is minimalistic but, if sensibly done, can be very easy to read. Just before get to grips with actually programming, I think it'd be nice tell you a little bit about the history. History of C C is an old (ie. well tried and tested language) Developed along with UNIX, the first really complete version of C came into being around 1973 and was used to rewrite the UNIX kernel from assembly to C. Derived from the typeless B which itself grew out of BCPL written in the mid 1960s. Incidentally there was no `A' - B is rumoured to be a contraction of `bon' which apparantly is a religion involving chanting magic words. Seminal text `The C Programming Language' by Kernighan and Richtie was written in 1978. Growth of computing in 1980's multiple incompatable variants of C - screwing up portability aspect - ANSI standard in 1990, all compilers should understand ANSI C. That's the idea, anyway. Data In 19xx whoever made what the book I was reading said was an `important contribution to the world of computer science' with the revelation that: Data structures + algorythms = programmes Since C is a programming language you can obviously do both data structures and algorythums. Kinda make sense to look at data before looking at code that acts on it so that's what we're going to do. Before you use any variable (a piece of data) it must be explicitally declared: (type) variable_name; The type of the data determines what you can store in it, common types include: int - storing integers float - storing decimal or `floating point' numbers char - storing characters Hence the following would be valid in C: Don't worry if you don't get the syntax we'll come to it later, I think it's always useful to have actual code examples to refer to. Generally a good idea to use the right type for the data: doing calculations with integers is a lot faster than with fp for example, so if you're not going to need fractional values use integers. Quick note: when you first declare a piece of data it's contents is undefined - it could be anything. Therefore it's important to set it to something before use. Maths Once you have your variables you can do maths on them. The stuff in stars are comments - they are ignored by the compiler I'm using them here to show what's contained in the variables after various operations. Comments are very useful for annotating bits of source so other (and yourself at later times) can quickly see what a particular piece of code is meant to do. In an Open Source environment they're also very useful for putting jokes in your source code so any one reading it can marvel at your rapier wit. C provides full range of basic maths operators: - Addition + Subtraction * Multiplication % Modulus (remainder) / Division -- Decrement (subtract 1) ++ Increment (add 1) As on the example a contraction of: variable = variable + other_variable; is variable += other_variable; The same construction can be used for - + * / % operators. Basic Structure of a C programme Take a closer look at example2.c First thing you'll probably notice is the large number of semicolons. C requires each statement to end with a semicolon. Once you've coded in C for a while this becomes automatic: when I was writing this talk I kept accidently ending sentences with semicolons rather than fullstops. At the top is a preprocessor include directive. This tells the compiler to include the file stdio.h which contains information about the printf function we use later on in the programme to print stuff on the screen. All C programmes contain a main function which contains the first instructions to be processed in the instructions to be carried out. The `void' keywords say that the main function doesn't take any input or return any output. We'll come back to input and output later. In C wiggly brackets are used to mark out logical blocks of code, in this case the main function. Everything contained within them is part of the function. We'll see more of these in just a second. C requires you to declare your data before you use it. More than that, it insists that any data you are going use in the function is declared before any instructions in the function are processed. So we declare the data here. The only other thing of interest are the printf statements. Printf is itself a function, like main, which takes various inputs, in this case the variables we want displayed and how we want that done. Printf allows you to put placeholders in a string which are filled by the contents of the variables you specify when it is printed on the screen. The main function ends with a closing wiggly bracket. Conditionals - if if ( expression ) { /* code */ } else if ( expression ) { /* code */ } else { /* code */ } The main conditional keyword in C is if. If the expression evaluates to a number other than 0 what is inside the wiggly brackets is processed and the rest ignored. Otherwise the `else if' is tested. If this succeeds the code inside that is processed. If not, the code inside the final else expression is evaluated. There is no requirement to have else if or else clauses, if's by themself or just if/else s are fine. The best way to see this is with an example This is a programme that, with a bit of modification I might use to decide what to do with my morning. Suspend your disbelief for a second and imagine that where the number of reference variables are set to values we instead have a magic piece of code which downloads the cartoons and works them out... In this case, unfortunately, Illiad is talking about sushi or dust instead of Linux again, the comp_p variable is not greater than the dust_p variable so the statement (here) evaluates to 0 and skip the code in the wiggly brackets. However, there is an else if clause so the programme checks the new expression. The bg_ref variable is set to 3 which is not 0 so the printf statement is executed. If the case had been that aftery2k had been berefit of BG references and therefore probably dull the programme would have ignored the code and dropped through to the final printf statement. That's a very simple example, but they is a lot more you can have in your expressions: explain example, noting: nesting statements - an if starts a new logical block of code which itself can have if's inside it. dropping {} - if there is only one statement `inside' the if function you can drop the {} brackets. use of && and || - logic - if two conditions are joined with && the result will only be true if both are true. If two conditions are seperated with || the condition will be true if either are true. implicit conversion in var1 < var4 - var1 is an integer and var4 a float. C works out what the float equivalent of the int and then does the comparison between the two numbers. Hence you get the results you would expect. sqrt is another function like printf. Non-trivial maths operations and most operations you will use are handled by functions. It returns the value of the square root of the parameter. Note we included the "math.h" include directive so we have access to this function. Absolutely any C statement can go in an if function as in the last example here. Rather than checking if var1 is equal to var2 it sets var1 to var2. The value of this expression is whatever var1 gets set to, so if you're not setting it to 0 whatever is inside the brackets is processed. If you do end up writing this it's probably a mistake (you meant ==) and gcc will actually helpfully warn you if it notices this. That's pretty much all there is to if - the other conditional statements in C are less used - switch/case is a cutdown version of if which can make your source look nicer if you are doing multiple equality tests on a variable (ie. does variable equal constant, if not does variable equal other constant, if not etc.) The other one is merely shorthand if/else statement and looks like a question mark and colon. Loops Probably of second most importance to programming after conditionals are loops. There are three main types of loop in C. The for loop for ( initialization ; condition ; increment ) { /* code */ } The while loop while ( condition ) { /* code */ } The do-while loop do { /* code */ } while ( condition ); All of these do largely the same thing - keep repeating the code (point) while a certain condition is met. If the condition is tested and fails (evaluates to 0) the loop exits and the code continues to the next statement after the end of the loop. Here are 3 loops which all print the numbers 0 up to 9. For loop: The first statement means that the first time the loop is reached the variable i will be set to 0. The programme then checks the second statement for truth. If it is true (does not evaluate to 0) it processes whatever is in between the wiggly brackets (here implicitally). When that is complete it performs the third statement (increase the value of i by one) then checks the second statement for truth again and so on. The while loop is just like the for loop above except that we've had to explicitally do the initialisation and increment ourselves (point). The do while loop is like the while loop except that instead of checking the condition at the beginning of the code block it checks it at the end. As such one iteration of the code will always take place. Since these examples do exactly the same thing you might ask why you should choose one over the other. It's a good question since intrinsically you can do the same with all of them. It really comes down to what makes nicer looking code. The syntax of for loops lends itself to the use of an index variable (like i in the opposite example) which is incremented every cycle and is tested in the condition. It provides a very neat syntax for loops like this. While and do while loops are better used when you either don't have an index variable or you don't increment it in regular increments. They are also good when you have a long complex condition. Bringing everything so far together Now we know all about loops and conditions we can actually write an almost useful programme (if you're a mathematician). The for loops provides numbers from 1 to 100. We then set the variable counter to this number subtract 1. The while loop checks if the counter is above 1 and that there is a remainder when you divide the test variable i by itself. If both of these are satisfied counter is decreased and the condition is retested. There are two conditions in which the programme can be when we reach the if statement. Either there was no number that divided i without a remainder (ie. a factor) in which case counter is set to 0 and i is prime. In this case we print the number. The other case is that a factor was found. Counter will be 2 or higher, the number is not prime so we don't print it. There is a small potential problem in this programme which you may be able to spot - if the compiler for some strange reason decided to check the i % counter statement before the counter > 0 statement on the first iteration (where i=1) we would get a divide by zero error since counter would be set to 0. Functions - pass by copy For the small trivial programmes we have been dealing with so far having a single main function which holds all our code is perfectly adequate. However as you can imagine, if we were actually writing a serious programme stretching to many hundreds of thousands of lines a single function programme would become extremely hard to read. In addition, if needed to use the same bit of code at two different places in the programme, say we had to test whether two completely different numbers were prime, we'd probably have to copy out the code twice which would be ugly and wasteful. Functions are chunks of code with inputs and outputs which can be used at any point in another function. When used well can make your code a lot easier to read and a lot shorter. In this example we have our familiar main function but we have also created a function called `square' which squares the number it is given and returns the answer. Let's consider the function square itself. The first line defines what the name, inputs and output of the function are: return_type function_name( input_type variable_name, input_type variable_name, ... ) Functions can only return one variable and the type of this is specified first. function_name is fairly obvious. Functions may take any number of inputs of different types, for instance the following is valid: float big_function( unsigned int count, char letter, int precision ); The function itself is a valid chunk of code and as such any variables you are going to use in it must be declared before any instructions are processed. The input variables are referred to by their names, ie. number and power. We declare a couple of new variables. These variables are described as `local' to the function power - they only exist for the duration of the time while power is running and are lost when it exits. The return() statement returns the variable inside the brackets as the function's output. In the main function we use the power function by calling it. The name is specified then the values to be input are placed inside the brackets and separated by commas. As you can see, either variables or constant values of the correct type can be used as inputs to the power function. printf is also a function and we call it in exactly the same way, the first input being the string we want to print on the screen and the other arguments being data values. printf is slightly special in that it can take a variable number of arguments, however you're really pretty unlikely to ever write a function which does this so forgive me if I skip over how to do this. Note that we can also use the output of the power function as an input to the printf function here . If we wanted we could use the output of one power function as the input of another by replacing say the 3 with a power() function call. Nesting calls like this can be confusing if you have very many nested calls and it's often best to use an intermediate variable like `buf' to hold the result. [point - buf is unnecessary could have done it like second printf call]. There is one last thing to mention about functions. So that the compiler can check if we call our functions correctly (ie. specify the right number of inputs etc.) it is necessary to tell the compiler what form the call will take. We do this by prototyping the function . Basically all a prototype is is a copy of the function declaration with a semicolon at the end. Strictly speaking actually naming the input variables is unnecessary but it's a good thing to do since, if you give your functions sensible names, it provides a quick reference as to what each input is used for. The is one unusual feature regarding the return type. You can specify a return type of `void' meaning the function doesn't return any value. Normally in such function you wouldn't have a return() statement. Pass by copy Consider this programme. One of the possibly confusing things about C is the way variables are passed to functions. The function receives a copy of the variable, not the variable itself. This means that any changes you make to the variable inside the function (in this case adding 12 to the input) are lost. So the above programme works by setting the variable `no' to 6. The func() function is called and receives the value of `no', 6, which it places in it's number variable. Number is increased by 12 then the function ends and the variable `number' ceases to exist. `no' is unchanged and it's value is printed. If this is tricky to remember, especially if you're used to languages like some versions of basic where you actually pass the variable to the function and any changes stick, think about doing the function func(10) as here. 10 is an integer constant and it wouldn't make sense to set the value of `10' to `22'. As a last example of functions, here is the prime numbers programme rewritten with a function which checks whether the input number is a prime or not and returns `1' or `0' as appropriate. The advantage with this approach is that we can check whether any number is a prime at any point in our programme. There are many prewritten functions which come with every C compiler. In order to use these simply put an include directive for the header file describing the function you want to use at the top of your C file. You can then call the function like any other. Arrays So far we've dealt with simple data types, mostly ints. However, often in programming you want to deal with a set of variables, not just one. For instance, if you were writing a spreadsheet (retch) programme you would presumably want a way of storing what was in your cells in a sensible way rather than having several thousand differently named variables. As you probably know this is done using an array. The simple way to declare an array in C is: type variable_name [ number_of_occurances ] So in the example we declare an array of 10 integer values. Next we set the integers to the numbers 0 to 9. Notice that we specify which member of the array we wish to use by specifying the number inside square brackets. Although we have declared an array of 10 integers they are numbered from: number[0] ... number[9] As such we use i=0;i<10 in the for loop. This might seem a little counterintuitive but it's actually quite logical and we will see when we discuss pointers. Arrays can be created for any type of data. Arrays of character data provide the C equivalent of a string of text and are a little special, we'll discuss them later. Coming back to writing a spreadsheet, it would be convienient if we had a direct way of representing a grid or table of data, ie. a 2 dimensional array. Here we create a 2 dimensional grid of 100 cells, ie. 10 rows and 10 columns. Next we set all cells to 0, using nested for loops. For every iteration of the outside loop the entire inside loop is processed, ie. i=0 j=0, j=1, ... j=9 [0][0] [0][1] [0][2] ... i=1 j=0, j=1, ... j=9 ... i=9 j=0, j=1, ... j=9 Then we set a few random cells to random values. Finally we print the grid. The unusual syntax in the first printf statement just tells printf to only print to two decimal places of accuracy. This printf statement prints a trailing space after each value but no new line. The second printf statement prints a newline after the inner loop has been processed meaning that we get the data for different i values on different lines making a rough grid. In C you're not limited to 2 dimensions - you can generally have as many dimension as you wish, providing that you have sufficient memory to store all the variables you have created. What do you imagine would happen if I was to compile and run the following programme? Answer: bad things. We define an array of size 10 by 10. However, we then attempt to give a value to what is the cell in the 11th column and 11th row. What we are effectively doing is writing a number to a piece of memory somewhere after the memory allocated to our array ends. Unfortunately, this area of memory could contain another piece of data or even a piece of code. Whilst with a little programme like this nothing untoward will probably happen, in a larger programme you could change something just enough to cause your programme to crash at some totally unrelated point in the code, say when a piece of data you overwrote is used again. Since you don't know what data you are overwriting when you run over the end of an array this can be a very nasty bug since the cause of the problem will not be apparant and you'll probably actually think the crash was the fault of some code around the area it crashed. This is why it's a good idea to check your programmes do not do this. You may wonder why the compiler doesn't check this on compilation or even insert some special code into the programme to check as you are going along. As for the first, the compiler simply turns code into machine instructions - it doesn't ever run the code so in slightly more complicated cases than the above it would have no way of knowing of what value an array access would take. A programme might require input from the user to determine which array member to set, for example. As for the later, it comes down to speed: C programmes are designed to be fast and making the programme check each array access as it comes across it would be a major overhead. Arrays are absolutely critical to most programmes. What we've gone through is the way of created arrays of fixed size. It's entirely possible to create, expand and destroy arrays during your programme's execution - this is dynamic memory allocation and although we won't get a chance to cover it in the talk it really is quite straightforward. [actually how you do it: int *array; array = malloc ( 10 * sizeof(int) ); /*Create an array of 10 entries*/ array = realloc ( array, 20 * sizeof(int) ); /*Double the size of the array whilst preserving the any values already within it*/ free(array); /*Free the memory allocated to the array - only necessary for arrays created with malloc, not fixed arrays as above*/ ] Strings C is fairly unusual in that it doesn't have a data type specifically for handling strings. Instead, strings are regarded as an array of `char's terminated with a char set to `\0'. However, the compiler does provide semantics for using string constants in C programmes. Once again I think the best way to see this is with an example: Firstly we create an array of 13 characters and set this equal to the string "I like trees". Although "I like trees" is only 12 characters long remember that strings have to have the terminating `/0' symbol so room for 13 characters in memory is required. After printing it we adulterate it slightly by changing the 8th character from `t' to `C' and place a termination symbol in the 9th character. Note that the string still occupies 13 char spaces in memory, however functions such as printf will print all the characters in a string until they reach the termination character so only 8 characters will be printed by the next printf statement. C provides a number of useful functions for dealing with strings and we use a couple of them here. The strcpy functions copies the contents of the second parameter into the first, here it copies the contents of msg (that is, all of it up to and including the termination character) into long_msg. It's important long_msg is long enough to hold the contents of msg otherwise we will get a memory overwrite in just the same way as with the arrays above. Next we optomistically append `a lot' to the end of long_msg. We declared long_msg to be 20 characters long at the start so this is safe. Pointers Pointers are what really defines the language C. If you understand pointers you basically understand how C works. If you don't understand pointers, on the other hand, you can get yourself in a right mess. Unfortunately pointers are often regarded as the most confusing part of the language. A pointer variable is a variable which contains the numeric memory address of another variable. We start by declaring an integer variable called number with the value of 12; Then declare an integer pointer. It's important that the pointer is of the appropriate type for the variable with which it will be used. Integer pointers are used with integer variables, for example. Pointers are declared with the type * notation. We then let the pointer equal the memory address of the integer number. The ampersand symbol when prepended to a variable like this means `the address of'. Now if we print we print the contents of the variables you'll see number has the value 12 and pointer has some big long memory address. We can use pointer to change the value of number indirectly. The syntax is as follows. If you prepend a star to the name of the pointer variable it is interpreted as `whatever this pointer points at'. In this case you can imagine the left side of that expression being replaced with `number'. When we print them again you'll see that the value of number has changed. What would have happened if we'd ignored the * before pointer? The assignment would then have been `set the value of pointer to 13'. Hence pointer would now have the memory address of 13 and would be pointing at whatever was in memory at position 13. If we were then to use a starred assignment to change the value of what pointer pointed at we would just be arbitrarily setting the data at memory address 13 to a new value. Since the data at address 13 could be absolutely anything this is probably a bad idea. In Linux or Windows NT (theoretically) programmes are given their own bit of memory to play with so you would probably just cause your programme to crash. In DOS on the other hand you could actually overwrite a critical bit of the operating system and you would be highly likely to hang or crash the entire computer. Which is why DOS programming is so much fun :) You might think the above programme is really quite pointless and you'd be, well, right. However, when you're dealing with arrays and strings you're actually dealing with pointers although you don't know it. Before discussing this I'll go through one very important implication of explicitally using pointers. Pass by reference You (hopefully) recall the fact that when you pass data to a function it receives a copy of the actual data and any changes you make in the function will not be reflected in the code which called the function. As I mentioned, it is possible to actually change the value of variables within the called functions. As you may have guessed the way you do this is instead of passing the data you pass a pointer containing the memory address of the variable you are interested in. You can then modify the data in either the calling or called function by using the * (dereferencing notation) we just went through. Here we declare the function swap to take two pointers to integers. We initialise a couple of integer variables, number_1 and number_2 to some values and print them. We then call swap with the address of both variables (note the & signs). The swap function stores the value of the integer x is pointing at then sets the integer pointed at by x to that pointed at by y. It then sets the integer pointed at by y to the stored value hold (previously what x pointed at). If you see what I mean... The main function finally printfs again to prove that it actually works. There are several important circumstances where you would want to use this `pass by reference' rather than pass by copy: - functions in C are limited to having a single return value. If you have a function which would naturally return two or more variables, require that pointers are passed as parameters. In your function you set the variables pointed to by these pointers to the your multiple `return values'. - passing large data structures as copies is very wasteful. Imagine you are writing a spreadsheet programme again and have your data in a structure holding amongst other things an array of 1000 by 1000 cells. You have a function which totals the value of all the cells and prints the value. Creating a copy of all the million odd variables before calling the function would take a long time, when you could just pass a pointer holding the memory location of the data structure and not copy anything. User-defined data types are exceptionally useful things and whilst I don't have time to cover them here if you do any serious C programming you will use them. If you're passing an array as a parameter of a function C actually forces you to use a pointer, something we'll cover in just a sec. Arrays as pointers When you declare an array in a programme what actually happens is that the array name becomes a pointer and is given the address of the first element of the array. When you specify an index in square brackets you are effectively saying `take the memory address of the first element and add on my index multiplied by the size in memory of the data type - there you will find the element I require' in a somewhat more concise form. Hence, when we pass an array as a parameter we are actually just passing a pointer to the first element. Inside the nesting function we can use our square bracket notation as normal. In this short programme we create an array of 10 integers, assign them the values 0 to 9 and print them. We then pass the array to another function which increments all the integers in the array by one. Note that since the function doesn't know how many entries there are in the array we have to explicitally tell the it by passing an integer variables containing this information. The main function finally prints another copy and we can see that all the variables have indeed been incremented. The three function declarations at the top are entirely equivalent. I prefer to use the first since it is the most explicit, the programme actually passes a integer pointer. You can do exactly the same thing with strings: This is a slightly more complicated example. We have a function reverse which reverses the string it is passed. It also returns a pointer to the reversed string allowing it to be nested inside printf statements: if it returned void we would have to reverse it and then print it in two seperate statements. All strings are terminated with the '\0' character so we do not need to pass the function the size of the string like we had to when passing arrays. The function strlen() returns the integer length of the string up to but not including the termination ('\0') character. We then go progressively through the string swapping the first and last characters, then the second and second last etc. Note that we use `length - 1'. strlen() returns the number of characters in a string but if we want to reference them in our programme we refer to the first character as 0 and therefore the last as (string - 1). That really brings me to the end of what I wanted to tell you about the basics of C. There are lots of wonderful things we haven't covered such as: header files and multiple source file support pointers to functions pointers to pointers userdefined data structures enumerations and unions dynamic memory allocation functions with variable numbers of arguments file i/o command line argument parsing but if you understand the basics these things should fall into place relatively quickly. Tools of the trade I'm sure you're bursting with enthusiasm to go write your own programmes now. I know I am. However, there are some tools which might be handy to get before you actually get down to the business of rewriting Linux to run on your wristwatch... Regardless of which operating system you are running there are enough free tools out there to allow you write C without spending masses on commercial software. Things you might need: A compiler Writing C without a compiler is likely to be fairly unproductive. Linux: Pretty much every Linux distribution comes with a fine C compiler, be it gcc, egcs or pgcc. They are very easy to use: gcc -o -g -Wall Linux also comes with many other useful things, including full documentation on most of the common functions (man strcmp etc.) Linux distributions can be obtained from: http://www.debian.org DOS/Windos: A port of gcc to DOS has been written and is freely downloadable. This is used in exactly the same way at gcc above. http://www.delorie.com VC: If you must, you can use MS VC++ to write C programmes. To explore the C language it's best to use the Wizard (yuck) to create a console app and go from there. A text editor Text editors are good things, especially emacs. Linux: Emacs, XEmacs, vi, vim are all good choices for a programmer's text editor. They support auto-indentation of source, context sensitive highlighting, lauching the compiler and catching errors etc. DOS: Emacs for DOS is probably the best bet. Windows: NTEmacs (notice any kind of pattern here?). If that doesn't take your fancy, try PFE, the Programmers' File Editor. If you must you could use an integrated development environment (a thing which combines a text editor with an interface to the compiler, debugger and make system). RHIDE for DOS is an option here, but to be honest, emacs does most of that stuff anyway. Debugger Debuggers allow you step through your programme one line at a time as it's running and do evil things to it in the process. gdb is the best bet for Linux or DOS and normally comes with gcc or DJGPP. VC++ also has a debugger. Don't rely on the debugger too much (it's too easy to do). It's often quicker to find bugs by examining the source first. To finish... The only, only, only way to learn to programme is to write something, watch it break, spend countless hours fixing it and then realise it'd have been _so_ much better if it had been done _that_ way and rewrite it completely. Programme something you want. I wrote a space wargame as my first non-trivial programme - I abadoned it eventually when early naive decision decisions eventually rendered the source complex and unpleasant to work with. However, I lot learn a hell of a lot during the ride. Tom