The terminology in the various imperative languages differ, but are pretty much the same.
C source files are compiled separately to yield object files. Object files are "tied" to the hardware -- they are not portable across different hardware platforms; the course code, however is portable - in theory. (One problem for HLLs deals with routines that are tied to a particular platform, such as graphics. C++ code developed using, say CodeWarrior for a PC, has Code-Warrior-specific system functions that may or may not be compilable using, say, Borland C++ or g++ for a PC. It is not only sad, but can be extremely frustrating if you need to port source files from one platform to another.)
To construct an executable file, that is, a file that can be run (or executed), all the object files must be linked (or loaded) together to produce one executable file. An executable file is as though all the source code for the program had been compiled as a single source file.
Basic (arithmetic) types Sun4 implementation
_
char 8 bits |
short 16 bits |
int 32 bits | integer
long 64 bits _|
float 32 bits |
double 64 bits | floating point
long double 128 bits _|
Arrays of these: <type> <array-id>[<int const>]...[<int const>]
Pointers to these: <type> * <id>
Arrays are always understood to be subscripted starting from 0, so
int x[10]; /* Entries are x[0], x[1], ..., x[9] */
The astute reader will have noticed immediately that there is no boolean
type. Integers serve as booleans: the integer 1 is interpreted as true; the
integer 0 is interpreted as false. It is not uncommon, when looking at C
code, to find the following "macro" definitions in some header file [see
below], say, boolean.h :
/* boolean.h */ #define TRUE 1 #define FALSE 0Here's some silly code to demonstrate the use of an int variable as a boolean.
#include "boolean.h"
. . .
int notallprocessed;
int y[1000];
int i;
...
notallprocessed = TRUE; /* the same as notallprocessed = 1; */
for (i=0; i< 100 && notallprocessed; i++) {
if (...) {
...
}
else {
...
notallprocessed = FALSE; /* the same as notallprocessed = 0; */
}
}
. . .
Memory has two properties: it has an address (or location), and each address (location) has a value (contents). Like a mailbox. Like a street with houses on it. A mailbox(house) has a number(address), and each mailbox(house) has different contents inside: mailboxes can have: letters, packages from amazon.com; houses can have Porches, Pomeranians, etc.
Memory also has two operations: we can read from memory, and write to memory. In order to read or write a value to memory, we must know the address we are reading from/writing to.
MEMORY
address value memory allocation
for data declarations in C
| . . . |
+----------+
2009 | 2008 | pv1 --+
+----------+ |
2008 | 0000 | v1 <--+
+----------+
2007 | 1010 | x[5]
+----------+
2006 | 0110 | x[4]
+----------+
2005 | 1000 | x[3]
+----------+
2004 | 1111 | x[2]
+----------+
2003 | 1010 | x[1]
+----------+
2002 | 0011 | x[0]
+----------+
| . . . |
The C compiler, when presented with an array declaration, say, int x[6],
allocates 6 consecutive memory locations for x. (For simplicity, we're not
looking at how many bits are required to represent the integer,
the number of bytes/word, etc. We're keeping it simple and assuming 1 of
"something".)
2002 is the address in memory of where the array x begins. The contents of memory location 2002 is the value of x[0]. We can certainly see that, if we have memory location 2002 as a "base address" from which to access the values of array x, then wee need only add an "offset" value to the base address to access other element values in the array. These offset values are what we commonly use when we think of "indexing into the array".
A pointer variable holds values that are the address of data structures (or primitive types) in memory.
Your morning mantra should be: A pointer variable stores an address.
Every pointer variable has an associated type. this type specified the type of the data object that the pointer addresses. The memory storage allocated to a pointer variable is the size necessary to hold the value of that type. The pointer variable's associated type specifies how the contents and size of the memory it addresses should be interpreted.
Let's look at some examples of variable and pointer variable declarations.
int x[6]; /* 6 integers for the x array */
int v1; /* v1 is an ordinary int variable */
int *pv1; /* pv1 is a pointer to an int */
...
pv1 = &v1; /* pv1 is assigned the address of v1 */
pv1 is a pointer variable; more specifically a pointer to an int variable.
A pointer variable is declared by prefixing the identifier with the
dereference operator "*". In a data declaration, read "*"
as "pointer to".
It makes it easier if you read all
data declarations right-to-left: v1 is an it; pv1 is a pointer to an
int.
The & operator is "address of" operator. Given any variable identifier,
& returns the address of that variable in memory!.
pv1 can be used to reverence v1 indirectly In order to reference v1 through pv1, pv1 must be made to "point to" v1. That is done above in the assignment statement. Since pv1 can only store an address to an int, we need to store into pv1 the address of v1. To do this we use the address-of operator.
pv1 = &v1;To find out the value of the variable that pv1 points to, we use the dereference operator "*", contents-of. Note, we only say "contents-of" in a command, not in a declaration.
The value of x[0] can be assigned, indirectly, the value of v1 using the pointer to v1.
v1 = 20;
x[0] = *pv1; /* assign to x[0] the contents of the address stored
in pv1 */
Here's some more "silly" code which assigns the value 20 to every array
element.
int i, v1;
int *pv1;
int x[6];
pv1 = &v1; /* pv1 points to v1 */
v1 = 20;
for (i=0; i<6; i++)
x[i] = *pv1; /* *pv1 is the value at the address pointed to by pv1 */
In fact, we can use either x[0] or *x to access the same memory location,
because, since x is a structured type, it contains the address of the
beginning of the 6 consecutive integers reserved for the array.
C is tied very closely to assembly language. So where did the developers of C, Kernighan and Ritchie, come up with the * and & operators? They originally developed C to be compiled to a machine that has * and & as the assembly language memory addressing operators.
Try compiling and running this silly program.
#includeint main() { int x[10]; int v; int *px; int i; for (i=0; i<10; i++) { /* the value of the array element is it's index */ x[i] = i; printf("%d\n", x[i]); } printf("\n"); px = &x[0]; for (i=0; i<1-; i++) { /* the value of the array element is 9-it's index */ *(px+i) = 9-i; printf("%d\n", *(px+i)); } printf("\n"); }
#include <stdio.h> /* include system header file */
#include "mystuff.h" /* include user header file */
/* compare to Java "import" */
#define CONST 2000 /* macro definition of a constant */
/* compare to Java static final */
int m,n; float x,y; /* global variable declarations */
char bf[CONST] /* global array declaration */
double fn1(double x, int y) /* function declaration: returns a double */
{float u; ... return x*u; } /* function body */
void fn2(float u, int j) ... /* function with no result */
...
...
int main(argv, char **argc) { /* main program declaration */
/* compare to Java */
/* public static void main(String[] args) */
int i,j; char str[128]; ... /* local declarations */
j = 17; /* assignment statement */
for (i=0; i<j; i++) { /* loop statement */
... str[i] ...bf[i]...} /* loop body with array references */
...
return 0;
}
/* ======= */
/* mainf.c */
/* ======= */
#include <stdio.h< /* standard input-output */
double fn1(double u, /* function declarations (prototypes) */
/* for fn1, fn2, */
double v, /* which are defined in auxf.c */
double w);
int fn2(int, double); /* prototypes don't need formal parameters */
/* just their types, but they are usually */
/* specified for clarity */
float fn3(int x, int y) /* defined here */
{ ... return (float) 3.0*x*x*y; }
int main() { /* defined here */
double a, b, c, d, f1;
float r2;
int j;
r1 = fn1(a,b,c);
j = fn2(j,f1);
... return (0); }
The source file auxf.c contains the code for functions fn1 and fn2, whose
prototypes are declared in mainf.c.
/* ====== */
/* auxf.c */
/* ====== */
#include <math.h>
double fn1(double u, /* definition of fn1 */
double v,
double w) {
... return u*v+w;
}
int fn2(int a, double b) /* definition of fn2 */
{ int return 7+a+b; }
One very large difference between C and Java is the fact that in C, the
name of the function does not have to the same as the name of the source
file! Also, in C, there is no concept of directory trees to search for
the class files - these need to be specified explicitly.
$ gcc -c -ansi mainf.c # produces mainf.o; -c means compile only $ gcc -c auxf.c # produces auxf.o $ gcc -ansi main mainf.o auxf.o -lm # produces main - the executable file $ main # run the program named mainThe third gcc command invokes the loader (linker). The inputs are: the required object files generated from the previous compiles and the math object library. The -lm switch says to load tin the math library (obtuse, isn't it?) main is the name of the resulting executable file. If the third command, instead, is
$ gcc -ansi mainf.o auxf.o -lmthe executable file will be written to a file names a.out, the default name for an executable file.
/* ======= */
/* mainf.c */
/* ======= */
#include <stdio.h> /* standard input-putout */
#include "auxf.h" /* auxf.h is in this directory */
float fn3(int x, int y) /* defined here */
{ ... return (float) 3.0*x*x*y; }
int main() { /* defined here */
... }
/* ====== */
/* auxf.h */
/* ====== */
external double fn1(double u, /* declarations for fn1, fn2, */
double v, /* which are defined in auxf.c */
double w);
external int fn2(int, double);
/* ====== */
/* auxf.c */
/* ====== */
#include <math.h>
double fn1(double u, /* definition of fn1 */
double v,
double w) {
... return u*v+w;
}
int fn2(int a, double b) /* definition of fn2 */
{ int return 7+a+b; }
What would happen if a compiler generated code for each source file, and put "absolute addresses" for the blocks of memory containing the instructions and data? Is this workable? No! Why not? Think about: what a (real) operating system does to execute a program; how absolute addresses would impact the linking of several object files, etc.