|
Operating Systems [22c:112] |
| Tutorial 3: A raw introduction to the C language you need |
|
One of the most important, but often hard to learn, characteristic of the C language
are the pointers. In C what we usually denote as a variable can in fact be: a simple
variable, a structure, or a pointer.
Simple variables
A simple variable can be an integer (int), a long integer (long), a signed character(char),
or an unsigned character. Refer to your C book for more explainations about types. The
typical declaration of an integer variable is
int a; // this is a comment
the variable a is a simple integer variable. This mean that an operation like
a = 50;
will write the value 50 into the memory space reserved for the variable a, and 20
in the memory space reserved for variable b. Another operation like
b = 20; c = a + b + 30;
will sum the current value of a to the current value of b to 30, and will
write the result in the memory space reserved for variable c.
Pointers
Declaring a pointer is easy. For example
int *b;
will declare the variable b as a pointer to an integer. Since we can know the memory
address assigned to a variable, inserting the & symbol before it's name, we can do
something like
d = &a;
and so b will contain the memory address in which a is contained. In a
similar way is possible to refeer to the memory address pointed by the variable, inserting
the symbol * before the variable name. This will allow us to do something like
*d = *d + 30;
the computer will read the number saved inside b, will take it as a pointer and
will go to read the value situated in that memory address. Then it will add 30 to that
value, and will save the result of the operation in the same memory address. As
consequence, if now we execute
printf("a=%d\n",a);
the output will be
a=80
Keep in mind that a declaration like
char *k[50];
has a special meanings. It's not a pointer to an array of chars, but an array
of pointers to chars. That is, k[1] is a pointer to chars, k[2] is a
pointer to chars, and so on...
Structures
A structure is very similar to a Java class, but without methods. You use structures when
you need to represent more complex data, and there are no predefined types that can help
you. For example, you can decide to represent a student in a structure that contain its
name, lastname, phone number, student id, and so on. Such structure would have been
declared in this way
struct student {
After the declaration of the structure, you can declare in your function how many
structure you need, as if it would have been a normal type. For example
char name[20]; int studentid; } struct student g;
will declare g as a structure student. You can access to the fields of g
using the dot symbol between the name of the variable and the name of the field
like
g.id = 123;
If you cannot declare at the beginning of your software all the structure you will need
things are a little bit more complicated. In the following section you will learn how
to reserve memory for a pointer, so for now feel free to skip what follow, go to learn
how a malloc() works, and come back later. If you cannot declare all the structure
you need at the beginning of your software, you will have to do it "on-the-fly" during
your software. Supposing that we have an empty pointer to a student structure declared as
struct student *p;
and that now we want to write in the memory that structure, we need to do few things.
First, we need to allocate the necessary memory. This can be done manually, calculating
how many bytes our structure need (DON'T DO THAT!!!) or automatically using the
sizeof() function. We will reserve memory with something like
p = (struct student*) malloc(sizeof(struct student));
sounds complicate, uh? Start from the right: sizeof() calculated the dimension of
a typical student structure and gave that number to the malloc() function. Then
the operating system found in memory a continuous space with the right dimension, where
to store our structure and returned it to the malloc() that returned its address.
The address is a generic pointer, so we "casted" it to a struct student pointer.
Then we saved the result in the variable p. Now all the generic rules to access
to the fields of a structure are still valid, but instead of using the "dot" symbol to
indicate the fields of the structure, you need to use -> as in
s->id = 246;
Remind also to free() that memory before exiting your software.
Strings and pointers
The hard part is that you can easily assign numbers to variables, but you can't do it so
easily with strings as you probably are use to do in Java. For example, something like
g.name = "Alessio";
will produce errors during the compilation. This is because in C strings are collections
of characters. In fact, one way to see a string in C is an array of character. That's the
reason because if you declare
char name[50];
you can refer to the variable name as a pointer to a string of 50 char. You better
remember this, because is very useful for scanning trought strings: printing
name[1] or name+1 will have the same effect. All the strings should be
declared as pointer to chars
char *s;
now s is a pointer to chars. You cannot directly write something in s,
because for now it's nothing, just an empty pointer. It doesn't point anywhere. To
reserve memory space where to store something you need to use the function
malloc(). This function accept one parameter: an integer that represent the size
of the memory space you need. Yes, you need to know it in advance. No arbitrary
length strings in C, I'm sorry. The use of malloc() is simple
s = malloc(50);
Now s contains the address of a memory space of 50bytes that has been reserved for
you by the operating system. The memory is not clean, you can find garbage in it, so you
better clean it using the function memset() in this way
memset(g,0,50);
This will write fifty 0s in the memory, starting from the address pointed by g,
in fact cleaning the memory. Even if it's not mandatory, every time you use the
malloc() function you should cast it. The word "cast" in programming slang
mean to force the type of a variable. The function malloc() return a pointer to
an area of the main memory, but it don't know for what will that memory be used. So, it's
good to force its output
s = (char*) malloc(50);
In this way, the malloc() function will return a pointer, that the operating
system will recognize as a pointer to chars. Good programmers always do that,
others doesn't. Another thing that you have to keep in mind is to free all
the memory you allocated before exiting the program. If you forget, that little piece of
memory you previously allocated and that contain your data, will be blocked until the
machine will be rebooted. To free the memory you can use the function free()
simply calling it with the name of the variable you want to free as
free(s);
Now s will point to nothing, or better, to NULL that is the word used in
C to define "nothing".
Even if now it may seem right, you still cannot write directly in that
variable. To work with string, you have to use the str* class of C function:
strcpy, strcat, strcmp, strdup, scanf. Type
man 3 strings in a shell to learn more about these functions.
Example #1: Calculator
This is a simple calculator. It cycle and accept numbers from the user input until one
of them is 0. When a 0 occurs it terminate and print the sum of the numbers.
#include <stdio.h>
int main (void){
int tmp; // declare integer variable tmp
int sum; // declare integer variable sum
sum = 0; // reset sum value
while (tmp!=0) { // while tmp is not 0
printf("> "); // print the prompt
scanf("%d", &tmp); // wait for user input, followed by ENTER
sum = sum + tmp; // sum the typed value to variable sum
}
printf("The sum is %d\n",sum); // print out the result
return 0;
}
Example #2: The greeter
This is another simple program, but is a good example. A pointer to chars is declared
(name) and some memory (50bytes) is reserver, cleaned, and assigned to it. Then a
question is displayed, and the answer of the user is waited. The answer is stored in
the memory previously reserved and pointed by name. A simple greeting is then
displayed, the reserved memory is free, and the software return.
#include <stdio.h>
int main (void){
char *name; // declare pointer to chars name
name = (char*) malloc(50); // reserve 50bytes in the memory
memset(name,0,50); // clean the memory
printf("What is your name? "); // print the prompt
fgets(name,50,stdin); // wait for user input and save it
printf("Goodmorning %s",name); // print the message
free(name); // free the reserved memory for var "name"
return 0;
}
Example #3: Family members
Another simple program that introduce you to more complex definitions. First, there is
a multiple declaration of chars pointer. Then, you can see a declaration with direct
assignment. 100 bytes are assigned to the variable a and cleaned. Then a prompt
is displayed, and the user is suppose to write a list of name separated by a comma.
The while cycle will now scan the string looking for commas, and will print every piece
of it on different line. Finally, when no more commas are in the strings, the software
free the used memory, and return.
#include <stdio.h>
#include <string.h>
int main (void){
char *a, *b, *tmp; // declare pointer to chars
int i=0; // declare integer i
a = (char*) malloc(100); // reserve 50bytes in the memory
b = a;
memset(a,0,100); // clean the memory
printf("> "); // print the prompt
scanf("%s",a); // wait for user input and save it
while (a!=NULL) { // cycle until a not equal to NULL
tmp = strsep(&a,","); // analyze string looking for commas
printf("[%d]: %s\n",i,tmp); // print block
i++; // increase i variable
}
free(b); // free used memory
return 0;
}
Example #4: Command line arguments
The purpose of this software is to display the command line arguments that you pass to
the software when you launch it. First thing to notice is the different declaration of
the function main() that now has some parameters. The name of these parameters is
not fixed, and you can choose the ones you want. Usually people use argc and
argv. I decided to use prm_cnt and prm because I don't want you to
think that there is only one way to do things in C. Modified the declaration, you can
access to the passed parameters easily using the pointers in the array prm.
The variable prm[0] is a pointer to a string containing the name of the file,
prm[1] is a pointer to the first parameter, prm[2] to the second, and so on.
The variable prm_cnt contain the number of given parameters plus one. As you can
see this piece of code do nothing of interesting. It just print out all the parameters
on separate lines. Then it check if there are more than 2 parameters and in that case,
check if the first and the second parameters are equal. If so, it print a simple message.
#include <stdio.h>
#include <string.h>
int main (int prm_cnt, char *prm[]) {
int i; // declaration of integer variable i
printf("Parameters: %d\n",prm_cnt); // print how many parameters are given
for (i=0; i<prm_cnt; i++) { // for each one of them
printf("[%d] %s\n",i,prm[i]); // print out the parameter
}
if (prm_cnt>2) { // if parameters are more than 2
if (strcmp(prm[1],prm[2])==0) { // if parameters 1 and 2 are equal
printf("[1] = [2]!\n"); // then print this sentence
}
}
return 0;
}
Example #5: A stupid shell
This is not the solution of your homework, neither something that is similar to a system
shell. This is only something that will help you to understand how the exec
function family work (man 3 execvp). After declaring a pointer to chars, and
reserving some memory for user input with clean it. Then we cycle with the while
until the command is not "quit". The software print the prompt and wait for user input.
When user press enter the execlp function try to execute the command in the real
shell, without parameters (the NULL is suppose to be a string, or an array, of
parameters). If it work, the stupid shell end and the other software will be executed,
if it doesn't work, the shell return the prompt. The function execlp(), and all
the other functions of its family transfer the control from the current program to
another, when called. So, when the other program terminate, also the "calling" software
should terminate. If the other program terminate with error, the execlp() function
return, and so doesn't allow the calling program to terminate. Since cmd is not
equal to quit, the prompt is displayed again.
#include <stdio.h>
#include <unistd.h>
int main (void) {
char *cmd; // declare a pointer to chars
cmd = (char*) malloc(100); // allocate 100 bytes for the pointer
memset(cmd,0,100); // clean the allocated memory
while (strcmp(cmd,"quit")!=0) { // while cmd is not equal to quit
printf("MyShell> "); // print "MyShell> " as a prompt
scanf("%s",cmd); // wait for user input and save it in cmd
execlp(cmd,NULL); // execute the command on the system
}
return 0;
}
|