Tuesday, June 2, 2009

3 - A little more advanced basics

===============================================================================
3 - A little more advanced basics.
===============================================================================
Here we will look at the basics of dynamic memory allocation, functions,
struct, typedef, define and some other new things.
So let's not waste time on weird elaborations of nothing, here we go with
the simple basics of dynamic memory allocation:
-------------------------------------------------------------------------------


As I've mentiond, to create a char pointer you need to allocate space for it,
like this:
char ptr[10];
That is a char pointer named ptr that can hold 10 bytes, or 80 bits (there's 8
bits in one byte), but the downside of that is that you have to know how much
memory it will use... and sometimes you dont know that.
So here is where the malloc() function comes handy, malloc means
memory allocation or memory allocate.
So first thing we need to create a pointer that has no memory allocated, that
we do like this:
char *ptr;
This would be the same as to say:
char ptr[];
Since a [] at the end means the same as a * in the beginning when you
declare pointers.
But *ptr; looks better.
The next thing you need is to allocate the memory to it dynamicly, that
you do something like this:
ptr = (char *) malloc ( strlen ( str ) );
Now what does that mean ?
First, the pointer equals the rest of the line, and the rest of the line
means, first we have (char *) which is called a cast.
A cast tells the program what sort of memory it should alocate, if it's
a void if it's an int or if it's a char or any other data type.
The next thing is the malloc( strlen(str) );, that means that it will
allocate the same amount of bytes as the str pointer is long.
Now that may seem confusing at this point since the pointer contains
nothing yet, but at the time it compiles the compiler will look at the whole
thing and see how long it will be.
A note is that the strlen() is a new thing here, let's make a simple
example of the strlen() function so you understand it (strlen needs string.h):
//----------------------------------------------------------
#include
#include
int main ( int argc, char **argv )
{
int i;
i = ( strlen ( argv[1] ) );
printf ( "The first argument was %i characters long.\n", i );
return 0;
}
//----------------------------------------------------------
Now, that should be understandeble, and if it isn't, try it.
(A note is that the program will 'segmentaion fault' if you dont
supply any arguments, this means that it will try to read memory that's
not there... to fix that you can add an "if ( argc != ..... etc.").

So, now we should be ready to make a little example program with
dynamic memory allocation, let's call the file program2.c
//----------------------------------------------------------
#include
#include
int main ( int argc, char **argv )
{
int i;
char *ptr;
if ( argc != 2 ) {
fprintf ( stderr, "Usage: %s \n", argv[0]);
return -1;
}
i = ( strlen ( argv[1] ) );
ptr = (char *) malloc ( i + 10 );
strcpy ( ptr, argv[1] );

printf ( "The argument was: %s and that is %i characters long.\n", ptr, i );
return 0;
}
//----------------------------------------------------------
Now let's take a look at what we have done here.
First we include the stdio.h header file.
The we make the main function that can import arguments and keep count of them,
and then open the function with a bracket.
Then we declare i as an int and ptr as a pointer with no memory allocated
at this point.
Then we check if the argument counter is 2 or not.
And if not, then we display the usage, and then we exit the program with
status unsuccessful, else we go on to:
i is the string lenth of argv[1].
The memory for ptr should be treated as char space, and the memory allocation
should be i (the string lenth of argv[1]) + 10 (so if argv[1] has a string
lenth of 10 it will allocate 20, this is a good precaution to make sure we
have enough memory allocated).
And then we copy the contents of argv[1] to ptr.
After that we print out the resault and exit with status success.
-------------------------------------------------------------------------------
This program executed looks like this:
alien:~$ ./program2 foo
The argument was: foo and that is 3 characters long.
alien:~$
alien:~$ ./program2 testing
The argument was: testing and that is 7 characters long.
alien:~$
-------------------------------------------------------------------------------
Now let's have a look at structures in C.
When you make a struct you should think of it as if you create your own
data type as char, int, void etc.
So let's go right to an example with a struct:
//----------------------------------------------------------
#include
int main ( void )
{
struct mystruct {
char buffer[20];
};
struct mystruct one;

sprintf ( one.buffer, "something" );
printf ( "%s\n", one.buffer );
return ( 0 );
}
//----------------------------------------------------------
So what does this mean ?
Let's go right to the new part:
.....
struct mystruct {
char buffer[20];
};
struct mystruct one;
.....
first we create a struct where test is the type, and it holds one member,
a char named buffer that can hold 20 bytes.
Note that the end bracket in the structure have to have a semi-colon after it.
And then we say "struct test one;" to create a pointer to the stuct so that
we can call the member(s) from the actual struct.
This means that "one.buffer" is the member buffer from the structure
pointed to by one, and will work as a pointer.
The "." dot, is a structure operator, and it connects the struct name and the
member name, so that it works as a pointer as just said.
This should make it clear.
Now, there is more then one way of doing this, so here is another example:
//----------------------------------------------------------
#include
int main ( void )
{
typedef struct mystruct my_struct;
struct mystruct {
char buffer[20];
};
my_struct *one;
sprintf ( one->buffer, "something" );
printf ( "%s\n", one->buffer );
return ( 0 );
}
//----------------------------------------------------------
So what does this mean ?
Let's go right to the new part again:
.....
typedef struct mystruct my_struct;
struct mystruct {
char buffer[20];
};
my_struct *one;
.....
typedef sorta renames "struct mystruct" to "my_struct", you need to do this
so you can declare "one" as a pointer to the struct function "my_struct *one;".
This means that you use "one->buffer" instead of "one.buffer" to point
to the member of the struct.
This will come to use later.
-------------------------------------------------------------------------------
Now let's take a short look at define, here is an example:
//----------------------------------------------------------
#include
#define ONE 1
int main ( void )
{
printf ( "%i\n", ONE );
return 0;
}
//----------------------------------------------------------
As you can see this is really simple, you just define "ONE" as "1",
you can say:
#define USA 01
#define SWE 46
#define AUS 61
Or whatever ..... and so the USA, SWE and AUS can be used as variables in
the code.
-------------------------------------------------------------------------------
Now let's take a look at functions, to this point we have only made one
main function in each program, but let's change that.
Actually the main function in a program should be as short as possible,
and only used to call for the other functions.
So let's make an example of a little program with more then one function.
Let's call the file program3.c
//----------------------------------------------------------
#include
int usage ( int i, char *myname );
int main ( int argc, char **argv )
{
usage ( argc, argv[0] );
printf ( "The argument was %s\n", argv[1] );
return 0;
}
int usage ( int i, char *myname )
{
if ( i != 2 ) {
fprintf ( stderr, "Usage: %s \n", myname);
return -1;
}
}
//----------------------------------------------------------
So now what does this mean, and why ?
First we inclide the standard input output header file as usual.
The we do something new, we declare a funciton, this you do as here,
simply by typing the full function name with what it may import
and a semi colon ";" after it.
Then we make a main function that may import the argument counter as
an integer and the actual arguements as characters, where on we open
the main function.
Then we call for the usage() function and export argc and argv[0] to it.
And after it has performed that function it will print out the printf line,
and then exit with success.
Now the usage() function ......
First we create it and import an integer and name it "i" and a char that
we name myname, and then open the funtion.
Then we ask if i (which was imported from argc and has argcs value),
is anything else then 2, and if it is anything else then 2, then
it will display the usage and return with status unsuccessful.
Note that the char myname will hold the value of argv[0].
And then we close that function.
-------------------------------------------------------------------------------
This program executed looks like this:
alien:~$ ./program3 foo
The argument was foo
alien:~$
alien:~$ ./program3
Usage: ./program3
alien:~$
-------------------------------------------------------------------------------
So now, what's the real point of functions ?
A function is really designed to import something and return something else.
This is the whole thing about a program with arguments aswell, so
you could say that a function is a program within the program.
Another good point of functions is that when you have really big projects
that are several thousen lines of code, then one file is to easy to
loose over view of, so then you can make one file for each function
and use a Makefile to compile it, I will explain this later.
Almost everything you use are functions, like printf is a function
described with syntax and all in stdio.h, that's why you need header files,
I will breafly explain header files and how to make them later.
Functions are good to use, and even if you're making a small program
you should always make a habit of making separat functions, and call them
from the main function.
-------------------------------------------------------------------------------
Here is another example with several functions, let's name the file functions.c
//----------------------------------------------------------
#include
int usage ( int i, char *myname );
void diplay1 ( char *argument );
void diplay2 ( void );
int main ( int argc, char **argv )
{
usage ( argc, argv[0] );
diplay1( argv[1] );
diplay2();
return 0;
}
int usage ( int i, char *myname )
{
if ( i != 2 ) {
fprintf ( stderr, "Usage: %s \n", myname);
return -1;
}
}
void diplay1 ( char *argument )
{
printf ( "Here is what's in diplay1() and argv[1] was: %s\n", argument );
}
void diplay2 ( void )
{
printf ( "Here is what's in diplay2()\n" );
}
//----------------------------------------------------------
First we include the header file like always.
Then we decalare the functions that we will use, (note that display 1 & 2
are void, because they dont return anything to the system).
Then we create a main function and open it.
Then we call for the functions, usage(), and export argc and argv[0] to it.
And then we call for diplay1(), and export argv[1] to that, and after that
we call for diplay2() to which we export nothing.
And then we return success and exit.
The usage function is the same as before.
Then we have the diplay1 function, which imports a char that we have named
argument, which will contain argv[1] because that's what we exported to it.
And what's in diplay1() should be understandeble, same goes for what's in
diplay2().
I will go deeper into what a function can return more then 0 or -1 in a while,
when I take up Makefiles etc.
At this point you should play around some with this and practice.
This brings us to the next section:

No comments:

Post a Comment