The Syntax
|
|
A compiler is a command line program used to change readable source code into binary executable programs or libraries. I say that they are command line programs because many of today's new IDE's hide this fact behind a nice dialog. Many of today's new programers do not know that the compiler they use is a command line program and can be used at the command line to tweak the options during compile time. Why do you need to know this? Most of the new IDE's that hide the compile process will compile all of your source code files with one set of predefined options, these options can be set for your project but they will apply globally to all source code files in the project. If you know your compiler is a command line program you can have your project compile with different options for each file by simply writing a script. If you are using a Microsoft Windows system you will need to write a batch file to to do this. If you are using a Linux or some form of UNIX like system you will most likely be editing a Makefile that is your compile script.
When a compiler runs it takes the source code file and
breaks it down in to small parts of code that it can easily analyzed.
These parts are then replaced by binary code that the computer can
understand. At this point the resulting binary file will be in a for
that will only run on the target system. Most compilers will by
default compile the code for the system you are running the compiler
on. There are cross compilers for other systems, for example if you
are running an Intel CPU in your
computer you may have a compiler that lets you make programs to run
on a Mac. If you are developing for embedded micorcontrollers you
will defiantly need a cross compiler.
Preprocessor
directives
Preprocessor directives are a way in C that let you
send instructions to the compiler and have these instructions
followed at compile time, or more accurately just before compile
time. They are instructions for the compiler of things to do before
compiling.
Here are some examples of Compiler directives
|
|
#include |
Instructs the compiler to insert the contents of a file here. #included "somefile" will cause somefile from the local directory to be included. #include <otherfile> will cause otherfile from a known library to be included. Most compilers will let you add the location of libraries so the #included "somefile" should only be used for files that are in your project.
|
|
|
#define |
This lets you set known values for the compiler. It can be used in two ways, to state a known values name like #define DEBUG, or to state a substituted value like #define MYVALUE 6. Note the space between the MYVALUE and the 6, this is how the compiler determines what is the defined name and what it is to substitute it for. Now in the source code every time the compiler finds MYVALUE it will replace it with a 6 before compiling that part. These values will only be set in this file and any files that #include this file. You can also place macros in a
define like so.
|
|
|
#if defined or #ifdef and #if ! defined or #ifndef used with #endif, can also use #else or #elif |
These directives let you test at compile time if some value was set or not set. You can use them to test for things and add in more code or instructions or leave them out. All #if blocks must end with a matching #endif so the compiler knows where to stop adding or ignoring code.
|
|
|
#pragma |
Pragma is unique in that it is not a standard. Almost every compiler I have used says that is does some thing different. This is as best as I can describe it. Pragma tells the preprocessor or the compiler that it is about to give it an instruction and if the preprocessor or the compiler don't understand it then should just ignore it. In your code you could have #pragma I think dogs have four legs and your compiler should do nothing about the line. Later on in the guide I will use #pragma pack(x) to alter memory management in structures. Most compilers should understand that command. |
|
|
|
|
There are more standard compiler directives and your compiler may have a few that are unique to it. These are the most commonly used ones and all that will be used in this guide. Search the help on your compiler for more information about compiler directives.
|
|
In the source code you can have comments that the compiler will ignore. They must be between /* and */ they can span as many lines as you want but for each /* there must be a matching */ after it.
|
|
|
|
|
Identifiers are names that you give to things in your code to make it easy to read what they do and how you use them. They are like proper names, they can contain numbers but can not start with a number. They can not contain any symbols except for the _ underscore symbol. Identifiers can be as short as one character or as long as you want but many compilers ignore characters beyond 32 long.
|
|
|
|
|
Before we get started with really coding we need to talk about blocks and lines. In C almost every line must end with a ; this it how the compiler finds the end of an instruction. The ; is used in other places also, it basically tells the compiler that it the end of some thing.
|
|
|
We don't use the
; at the beginning of
blocks. Blocks are things that start with {
and end with }. They are used to separate levels of
processing. Blocks inside blocks are deeper levels in then the block
that contains them. They are used to state the contents of functions,
loops and conditionals.
|
|
|
Each { must be
matched with a } or errors will happen during compile time.
|
|
In the examples above we used the int data type. Data types are how the compiler identifies variables. As computers become more and more powerful the details on some of the data types change, they also change between hardware architectures. You will want to confirm this information for your compiler. In C before you can use a variable you must declare it and tell the compiler what type it is. By default most data types are signed meaning they can be a positive value (greater then zero) or a negative number (less then zero). For these signed types you can use the unsigned key word to for them to only be positive numbers. This usually doubles the size of the maximum value that variable can hold. For example the char is a value from -127 to 127 but if you declare it as unsigned char it can hold a maximum value of 255, that is a count of 256 values ranging from 0 to 255.
|
|
char |
Short for character, this is always 8 bits. The value is from -127 to 127. Is mostly used to represent a letter or some other character of the ASCII character map. |
|
|
int |
Short for integer, this is always a whole number. It can be very large, check your compiler for exact size. |
|
|
float |
This is a floating point number. It can represent very large numbers or very small numbers because it uses an exponential multiplayer. The resolution of the number is limited by the size of the data type. |
|
|
long |
Used as a larger version of int. It is a whole number. |
|
|
double |
Used as a larger version of float. It is a floating point number. |
|
|
short |
Used as a smaller version of int. It is a whole number. |
|
|
void |
Used my it self to represent nothing. If used as a pointer it can be used to represent some thing that will be defined later. |
|
|
Type casting is a way of telling the compiler to treat a variable as if it was a different type of variable. You can only type cast the variable on the right, not the left. In the example we have pointers that we type cast as other pointers. The void pointer means it is a pointer to an un-specified type, a generic pointer. We also have a char type that can hold a value from -128 to 127 and an unsigned char that can hold a value from 0 to 255. What is the value of A at the end?
|
|
|
|
|
Operators are how the compile know what you are wanting to do with a variable. These are examples of how you can set, mask and test values. Better examples of how to use these will be shown throughout the code to follow.
|
|
= |
A single equal sing is used to set the value of a variable. The variable to the left is set to the value to the write. Example A = 1. The variable must not be a constant, like 1 = 4 would cause an error. What is on the right can be almost any thing, like a math equation or another variable or a pointer. |
|
== |
This is a test, the answer will be true if the left side equals the right side. Example A == 4 would be a true if A was 4, if not then the answer would be false. |
|
|
|
+ |
Adds the left value and the right value and returns the answer. Example A = 5 + 2 |
|
|
- |
Subtracts right from left and returns the answer. Example A = 5 - 2 |
|
|
/ |
Divides left by right and returns the answer. Example A = 10 / 2 |
|
|
% |
Divides left by right and returns the remainder as the answer. Example A = 11 % 2, A now is 1 |
|
|
! |
Performs a logical NOT on the right side value. If the right side value is 0 the answer is 1, else the answer is 0. |
|
|
++ |
Increment the value of the variable by 1. Can be used before or after the variable. The difference is only important when you are testing the value in the same line. Example if(A++) tests the value of A then increments it by one, if(++A) would increment the value of A by one and then test the value of it. |
|
|
-- |
Just like ++ only subtract by one. |
|
|
+= |
To add the value on the right to the value on the left. Example A = 2; A += 5; then A is now 7 |
|
|
-= |
Just like += only subtracting. |
|
|
/= |
Just like += only dividing |
|
|
*= |
Just like += only multiplying |
|
|
~ |
Performs a bitwise NOT on the right side value. For example, if binary value on the right side is 11000111 the answer is 00111000. |
|
|
* |
Used to denote a pointer. The identifier to the right will be come a pointer if it is being declared or used as a variable if it is a pointer and not being declared. |
|
|
& |
Performs a bitwise AND with the values on the left and right, then returns the answer. For example A is a boolean value of 11000111 and B was 01111100 then C = A & B would make C value 01000100 |
|
|
| |
Performs a bitwise OR with the values on the left and right, then returns the answer. For example A is a boolean value of 11000101 and B was 01101100 then C = A | B would make C value 11101101 |
|
|
|| |
Performs a logical OR with the values on the left and right, then returns the answer. For example if A is not zero and B is zero A | B would be true. |
|
|
&& |
Performs a logical AND with the values on the left and right, then returns the answer. For example if A is not zero and B is zero A && B would be false. |
|
|
!= |
Tests if value on left is not equal to value on right. It is a short way of testing !( A == 4) where if A was 4 the answer would be false |
|
|
<< |
Left bit shift the left value for the number of bits in the right value. Example if A is 00000010 then B = A << 3 would make B value 00010000 |
|
|
>> |
Right bit shift the left value for the number of bits in the right value. Example if A is 00010000 then B = A >> 2 would make B value 00000100 |
|
|
<<= |
Left bit shift the left value for the number of bits in the right value. Example if A is 00000010 then A <<= 3 would make A value 00010000 |
|
|
>>= |
Right bit shift the left value for the number of bits in the right value. Example if A is 00010000 then A =>> 2 would make A value 00000100 |
|
|
<= |
Test if left value is less then or equal to right value. |
|
|
>= |
Test if left value is greater then or equal to right value. |
|
|
^ |
Performs a bitwise XOR with the values on the left and right, then returns the answer. For example A is a boolean value of 11000101 and B was 01101100 then C = A ^ B would make C value 10101001 |
|
|
^= |
Performs a bitwise XOR with the values on the left and right, then returns the answer in the value on the left. For example A is a boolean value of 11000101 and B was 01101100 then A ^= B would make A value 10101001 |
|
|
-> |
Defines resolution when the left value is a pointer and the right value is a member. Example MyPointer->ItsMember |
|
|
[ ] |
Defines address offset from left value for as far as the value in the braces. Example MyArray[4] points to the fourth address beyond the starting point of MyArray |
|
|
. |
Defines resolution when the left value is a variable and the right value is a member. Example MyStruct.ItsMember |
|
|
& |
Resolve address of a variable. This is used to get the address as if the variable was a pointer. |
|
|
|
|
|
|
After declaring a variable you will often want to set the value of it. To do this you can use the above operators to assign values to these variables. Lets try a few.
|
|
|
Question 1: Can you figure out the value of D?
Question 2: Can you figure out the value of B?
|
|
After having set the value of a variable we would most likely want to test it using a conditional. The most command conditional is an if question. Often it is called an if statement, I feel this is wrong because you do not state any thing with an if, you ask a question with an if. You ask if some thing is true then do this. The thing you want it to do can be in a block immediately following the if question, or it could be a one line instruction.
|
|
|
There is another common conditional test method
called a case block or a switch case. It is like having many if tests
that each check one possible answer. The switch line contains the
test then inside the block there is a case statement for every out
come that you want to have a unique reaction for, if the entire
switch case is completed and no result happens then the default may
be selected. Many cases can happen, for this reason if at the end of
a case you want to not test the condition against any more cases you
must use the break key word.
|
|
|
As seen in the code above option 2 did not have a break after the code for it. This means that if A is 3 it will run the 3 code, but if A is 2 it will run the option 2 code and then the option 3 code. There are times when we want this to happen, but be careful you do not leave out a break by accident.
|
|
Loops are how you make things happen many times. For example if you needed to sort a list of peoples names you could look through the list from the top to the bottom and if you found two names out of order, swap them. Then start at the top of the list again and do this until you can get all the way through the list with out finding any names out of order. This is called bubble sort, it is very slow but is a good thing to learn programming with. One thing that all loops have in common is an exit condition, this is how the loop stops looping. Make sure that your exit condition can happen or your loop will go on and on for ever. Let's look at some looks and how they work.
for loop: A for loop is a counting loop. It has three parts that you can tweak, start value, exit condition and stepping value. It if formatted like so for(start;exit;step) { the repeated action }. The exit is on false, as long as your exit condition is true the loop will continue to run. Let's see some examples.
|
|
|
while loop: Is a loop that will do some thing until the condition is false.
|
|
|
In the first loop we count A from 50 down to 5 but in the second loop we keep counting it down? Will it ever end? This is a bit of a trick, remember that the int is a signed variable, it will count in to the negative numbers. On most systems (proper systems) this number will count down until it reaches it's lowest negative number then one less from that will make it it's largest positive number then the loop will exit. Be careful on some odd systems you may find that the number will no wrap around like that and you could get cough in a never ending loop.
A while loop will only run the block code if the conditional is true, if the conditional is false before you reach the while it will ignore that code and skip to the code immediately following the block or if there is no more code it will end the block that it is in. But if you want the code to run once even if the conditional is false then you use a do while loop like so.
|
|
|
|
|
Functions let you write better code, easy to read code, re-usable code. Simply put a function is a group of instructions that do some thing and they are all placed in one location that you just call it when you need some thing to happen. In C there is always one very important function, main, it is the starting function. From main you can call all your functions and others that are in libraries. Lets write some functions!
This is a very useless one, we don't want to do B++ any more so we are going to write a function to do it.
|
|
|
In that example we used
B as a global variable, that is a bad thing to do. Try to avoid
global variables at all cost. Using them may cause errors, an in when
two files have a global variable of the same name, it also makes it
much harder to track down bugs, 90% of programming is debugging code
that for some reason does not quite work right. A much better way is
to pass the value of B to our function and return B back, like so.
|
|
|
As we see here, the IncormentValue function was declared as an int function, this means it will be returning an integer via the return command. It could have just as easily been a char, float, double or long or anything else but we wanted to return an int so we made it an int function. If your functions do not use the return at the end of the function you should make the function a void function as in the next example. If you do not say the function is any thing as seen in example the_syntax-functions_01.c the compiler may make it a void or an int function, guessing as to what your code will do is not good, always make it return type or a void.
Do you see how we passed the value of B as a new variable called SomeValue? Then we added one the the value of SomeValue and then returned it. This is called pass by copy. There is a another way we could have done it, pass by reference where we pass the pointer to B instead.
|
|
|
Do you see that? We did
not need to return the value because the pointer to B had it's value
changed. We looked at the value in the address and changed it. Why is
SomeValue in brackets? On most compilers I don't thing that is
needed, but on a few the ++ may add to the address of the pointer
before the * is used to make it the value of the address. The
brackets force the value to increase, not the address.
A function can only return one value, but it may take many values as arguments. If you need to have many values returned you can return a pointer to a struct, structs will be explained later. For now just think of it as a pointer to a bunch of things all in one container, like a bucket. Often you will find that you pass in a pointer for a variable just so it can be filled with data, this is also like a return only it does not use return, it uses pointers. This next example is just a list of functions, these are called prototyping function declarations, you will learn about them later. Here are examples of function prototypes with various combinations of arguments and returns.
|
|
|
Why is this example a .h file? Remember what I said about header files. This is a big hint for prototyping, also if you want a big hint, see if there is a matching .c file for this one in the code directory.
You can also use functions like pointers, we will look at pointers next but you take a look at this example first and then come back to it after learn about pointers.
|
|
|
In the above example we have four functions, we use them but did not call them directly. We used a pointer to the functions and called them via the pointer. To do this all the functions had to return the same thing and take the same arguments. They all returned nothing and took no arguments but they easily could have as long as it was the same for all of them.
|
|
A pointer is like an address on a house. If you think of the house as the variable then your house address is like a pointer. Pointers are variables that contain a number, the number is actually a memory address as to where some data can be found. Every Architecture will have a different way of addressing memory and every OS that can run on that system may add a different way of dealing with pointers but for a fictitious computer we will look at what this would be. Here is a memory map for the CPU of a SuperDuper 9000 computer.
|
|
|
|
In memory location 0003 we have a pointer that points to memory location 0007, and in location 0007 we have our data the number 2. Let's look again at the earlier problem where we changed the value of B using a pointer E.
|
|
|
In functions we also did some passing back and forth of pointers. Now let's look at the most common pointer of all, the string. In C, unlike Pascal or BASIC you don't have strings, not in the way you may already know them. In C we use a char array. If you looked at the file the_syntax-functions_04.c file you would have seen a few char arrays already. We declare a char array like so, char MyString[20]; Inside the [] we set the size of the char array, it hold 20 chars for this example. When we declare it like this we tell the compiler to make room in memory for 20 chars, in this program that size will never change. If we do not know how big the string will be we could declare it like this char MyString[]; or char *MyString; In all three cases the variable MyString is just a pointer. When we want to use the string for some thing we need to do this MyString[0]. Why is that? In C, for most things we start all counters from 0. Why? That is the way it works in binary and C is more of a low level language like ASM then things like Pascal or BASIC.
Lets look at some arrays and how we could use them.
|
|
|
|
|
As you know from the above section on data types there is a good selection of types for you to declare your variables as, but the compiler does not stop there. Your C compiler will also let you make your own data types at compile time, and later on in the guide we will look at how to define data types at run time. Here we will look at struct's, unions, arrays, bit fields and the reserved word typedef.
Arrays are a group of varibles of the same type all in a row one after the other. They can be indexed using []. We have looked at char arrays that we use as strings but we can use arrays for any thing. We declare them the same way we do with char arrays, type var_name[number];. From then on we can easly go thourgh the index to access each variable.
|
|
|
A structure is defined by the reserved word struct. It is used simply to create a data type that is a container of other data types, a collection of data types.
|
|
|
Notice how we use the . (dot modifier) for the the struct variable and we used the -> (arrow modifier) for the pointer to the other struct variable. When we defined ClientData the compiler made space in memory for it and it is ready to use, but the NewClient is only a pointer to the data type we can not use it yet. We use the malloc (memory allocate) command to ask the system for memory space then we can use the NewClient When we are done with the memory space we use free to return the memory to the system, failure to do so causes memory errors and will crash your program. Both the variable ClientData and NewClient are instances of the structure TClientData. When I defined the structure I put a T in front of the name this is because the instance name can not be the same as the structure defined type name. The T simply stands for type, but I could have used S for structure, I could have called it anything unique but to do it this way makes it easier to read.
A union is a way of making data varibables share the same space. This is mostly used when you need to copy data in to memory location in one way but access the data in a different way.
|
|
|
A bit field is a structure of bits that you make any size you like. So instead of having a struction of eight booleans that take up eight bytes of space you can can do the same thing in only one byte of space. Very usefull in embedded systems. Notice the Count it is 3 bits in size so we can use it as a variable that can range from 0 to 7.
|
|
|
|
Answers to above questions. |
|
D = 10.000000
B = 7, why? Because E became a pointer
to B and we changed the value of B via the pointer.