| << Prev | Beej's Guide to C | Next >> |
This is the section where we flesh out a bunch of the stuff we'd done before, except in more detail. We even throw a couple new things in there for good measure. You can read these sections in any order you want and as you feel you need to.
Pointer what? Yeah, that's right: you can perform math on pointers. What does it mean to do that, though? Well, pay attention, because people use pointer arithmetic all the time to manipulate pointers and move around through memory.
You can add to and subtract from pointers. If you have a pointer to
a
/* This code prints: */
/* 50 */
/* 99 */
/* 3490 */
int main(void)
{
int a[4] = { 50, 99, 3490, 0 };
int *p;
p = a;
while(*p > 0) {
printf("%i\n", *p);
p++; /* go to the next int in memory */
}
return 0;
}
What have we done! How does this print out the values in the array?
First of all, we point p at the first element of the array.
Then we're going to loop until what p points at is less than
or equal to zero. Then, inside the loop, we print what p is
pointing at. Finally, and here's the tricky part, we
increment the pointer. This causes the pointer to move to
the next
In this case, I've arbitrarily decided (yeah, it's shockingly true: I just make all this stuff up) to mark the end of the array with a zero value so I know when to stop printing. This is known as a sentinel value...that is, something that lets you know when some data ends. If this sounds familiar, it's because you just saw it in the section on strings. Remember--strings end in a zero character ('\0') and the string functions use this as a sentinel value to know where the string ends.
Lots of times, you see a for loop used to go through pointer stuff. For instance, here's some code that copies a string:
char *source = "Copy me!";
char dest[20]; /* we'll copy that string into here */
char *sp; /* source pointer */
char *dp; /* destination pointer */
for(sp = source, dp = dest; *sp != '\0'; sp++, dp++) {
*dp = *sp;
}
printf("%s\n", dest); /* prints "Copy me!" */
Looks complicated! Something new here is the comma operator (,). The comma operator allows you to stick expressions together. The total value of the expression is the rightmost expression after the comma, but all parts of the expression are evaluated, left to right.
So let's take apart this for loop and see what's up. In the initialization section, we point sp and dp to the source string and the destination area we're going to copy it to.
In the body of the loop, the actual copy takes place. We copy, using the assignment operator, the character that the source pointer points to, to the address that the destination pointer points to. So in this way, we're going to copy the string a letter at a time.
The middle part of the for loop is the continuation condition--we check here to see if the source pointer points at a NUL character which we know exists at the end of the source string. Of course, at first, it's pointing at 'C' (of the "Copy me!" string), so we're free to continue.
At the end of the for statement we'll increment both sp and dp to move to the next character to copy. Copy, copy, copy!
This one isn't too difficult to wrap your head around, but there are some strange nuances to it that you might see out in the wild. Basically typedef allows you to make up an alias for a certain type, so you can reference it by that name instead.
Why would you want to do that? The most common reason is that the
other name is a little bit too unwieldy and you want something more
concise...and this most commonly occurs when you have a
struct a_structure_with_a_large_name {
int a;
float b;
};
typedef struct a_structure_with_a_large_name NAMESTRUCT;
int main(void)
{
/* we can make a variable of the structure like this: */
struct a_structure_with_a_large_name one_variable;
/* OR, we can do it like this: */
NAMESTRUCT another_variable;
return 0;
}
In the above code, we've defined a type,
You're probably also wondering why the new type name is in all caps. Historically, typedef'd types have been all caps in C by convention (it's certainly not necessary.) In C++, this is no longer the case and people use mixed case more often. Since this is a C guide, we'll stick to the old ways.
(One thing you might commonly see is a
You can also typedef "anonymous"
typedef struct {
int a;
float b;
} someData;
So then you can define variables as type
Sometimes you have a list of numbers that you want to use to
represent different things, but it's easier for the programmer to
represent those things by name instead of number. You can use an
(I should note that C is more relaxed that C++ is here about
interchanging
Note that an
Here are some enums and their usage. Remember--treat them just like
enum fishtypes {
HALIBUT,
TUBESNOUT,
SEABASS,
ROCKFISH
};
int main(void)
{
enum fishtypes fish1 = SEABASS;
enum fishtypes fish2;
if (fish1 == SEABASS) {
fish2 = TUBESNOUT;
}
return 0;
}
Nothing to it--they're just symbolic names for unique numbers. Basically it's easier for other programmers to read and maintain.
Now, you can print them out using %d in printf(), if you want. For the most part, though, there's no reason to know what the actual number is; usually you just want the symbolic representation.
But, since I know you're dying of curiosity, I might as well tell you
that the
If you want, though, you can override any or all of these:
enum frogtypes {
THREELEGGED=3,
FOUREYED,
SIXHEADED=6
};
In the above case, two of the enums are explicitly defined. For FOUREYED (which isn't defined), it just increments one from the last defined value, so its value is 4. You can, if you're curious, have duplicate values, but why would you want to, sicko?
Remember how, many moons ago, I mentioned that there were a number of
ways to declare
/* standalone: */
struct antelope {
int legcount;
float angryfactor;
};
/* or with typedef: */
typedef struct _goatcheese {
char victim_name[40];
float cheesecount;
} GOATCHEESE;
But you can also declare variables along with the
struct breadtopping {
enum toppingtype type; /* BUTTER, MARGARINE or MARMITE */
float amount;
} mytopping;
/* just like if you'd later declared: */
struct breadtopping mytopping;
So there we've kinda stuck the variable defintion on the tail end
of the
And, just when you thought you had it all, you can actually omit the struct name in many cases. For example:
typedef struct { /* <--Hey! We left the name off! */
char name[100];
int num_movies;
} ACTOR_PRESIDENT;
It's more right to name all your
I've been lying to you this whole time, I must admit. I thought I could hide it from you and not get caught, but you realized that something was wrong...why doesn't the main() have a return type or argument list?
Well, back in the depths of time, for some reason, !!!TODO research!!! it was perfectly acceptable to do that. And it persists to this day. Feel free to do that, in fact, But that's not telling you the whole story, and it's time you knew the whole truth!
Welcome to the real world:
int main(int argc, char **argv)
Whoa! What is all that stuff? Before I tell you, though, you have to realize that programs, when executed from the command line, accept arguments from the command line program, and return a result to the command line program. Using many Unix shells, you can get the return value of the program in the shell variable $?. (This doesn't even work in the windows command shell--use !!!TODO look up windows return variable!!! instead.) And you specify parameters to the program on the command line after the program name. So if you have a program called "makemoney", you can run it with parameters, and then check the return value, like this:
$ makemoney fast alot $ echo $? 2
In this case, we've passed two command line arguments, "fast" and "alot", and gotten a return value back in the variable $?, which we've printed using the Unix echo command. How does the program read those arguments, and return that value?
Let's do the easy part first: the return value. You've noticed that
the above prototype for main() returns an
int main(void)
{
int a = 12;
if (a == 2) {
exit(3); /* just like running (from main()) "return 3;" */
}
return 2; /* just like calling exit(2); */
}
For historical reasons, an exit status of 0 has meant success, while nonzero means failure. Other programs can check your program's exit status and react accordingly.
Ok that's the return status. What about those arguments? Well, that
whole definition of argv looks too intimidating to start
with. What about this argc instead? It's just an
$ makemoney fast alot # <-- argc == 3 $ makemoney # <-- argc == 1 $ makemoney 1 2 3 4 5 # <-- argc == 6
(The dollar sign, above, is a common Unix command shell prompt. And that hash mark (#) is the command shell comment character in Unix. I'm a Unix-dork, so you'll have to deal. If you have a problem, talk to those friendly Stormtroopers over there.)
Good, good. Not much to that argc business, either. Now
the biggie: argv. As you might have guessed, this is where
the arguments themselves are stored. But what about that
$ makemoney somewhere somehow $ # argv[0] argv[1] argv[2] (and argc is 3)
Each of these array elements, argv[0], argv[1],
and so on, is a string. (Remember a string is just a pointer to a
I haven't told you much of what you can do with strings yet, but check out the reference section for more information. What you do know is how to printf() a string using the "%s" format specifier, and you do know how to do a loop. So let's write a program that simply prints out its command line arguments, and then sets an exit status value of 4:
/* showargs.c */
#include <stdio.h>
int main(int argc, char **argv)
{
int i;
printf("There are %d things on the command line\n", argc);
printf("The program name is \"%s\"\n", argv[0];
printf("The arguments are:\n");
for(i = 1; i < argc; i++) {
printf(" %s\n", argv[i]);
}
return 4; /* exit status of 4 */
}
Note that we started printing arguments at index 1, since we already printed argv[0] before that. So sample runs and output (assuming we compiled this into a program called showargs):
$ showargs alpha bravo
There are 3 things on the command line
The program name is "showargs"
The arguments are:
alpha
bravo
$ showargs
There are 1 things on the command line
The program name is "showargs"
The arguments are:
$ showargs 12
There are 2 things on the command line
The program name is "showargs"
The arguments are:
12
(The actual thing in argv[0] might differ from system to system. Sometimes it'll contain some path information or other stuff.)
So that's the secret for getting stuff into your program from the command line!
Welcome to...the Nth Dimension! Bet you never thought you'd see that. Well, here we are. Yup. The Nth Dimension.
Ok, then. Well, you've seen how you can arrange sequences of data in memory using an array. It looks something like this:
!!!TODO image of 1d array
Now, imagine, if you will, a grid of elements instead of just a single row of them:
This is an example of a two-dimensional array, and can be indexed by giving a row number and a column number as the index, like this: a[2][10]. You can have as many dimensions in an array that you want, but I'm not going to draw them because 2D is already past the limit of my artistic skills.
So check this code out--it makes up a two-dimensional array, initializes it in the definition (see how we nest the squirrely braces there during the init), and then uses a nested loop (that is, a loop inside another loop) to go through all the elements and pretty-print them to the screen.
#include <stdio.h>
int main(void)
{
int a[2][5] = { { 10, 20, 30, 40, 55 }, /* [2][5] == [rows][cols] */
{ 10, 18, 21, 30, 44 } };
int i, j;
for(i = 0; i < 2; i++) { /* for all the rows... */
for(j = 0; j < 5; j++) { /* print all the columns! */
printf("%d ", a[i][j]);
}
/* at the end of the row, print a newline for the next row */
printf("\n");
}
return 0;
}
As you might well imagine, since there really is no surprise ending for a program so simple as this one, the output will look something like this:
10 20 30 40 55 10 18 21 30 44
Hold on for a second, now, since we're going to take this concept for a spin and learn a little bit more about how arrays are stored in memory, and some of tricks you can use to access them. First of all, you need to know that in the previous example, even though the array has two rows and is multidimensional, the data is stored sequentially in memory in this order: 10, 20, 30, 40, 55, 10, 18, 21, 30, 44.
See how that works? The compiler just puts one row after the next and so on.
But hey! Isn't that just like a one-dimensional array, then? Yes, for the most part, it technically is! A lot of programmers don't even bother with multidimensional arrays at all, and just use single dimensional, doing the math by hand to find a particular row and column. You can't technically just switch dimensions whenever you feel like it, Buccaroo Bonzai, because the types are different. And it'd be bad form, besides.
For instance...nevermind the "for instance". Let's do the same example again using a single dimensional array:
#include <stdio.h>
int main(void)
{
int a[10] = { 10, 20, 30, 40, 55, /* 10 elements (2x5) */
10, 18, 21, 30, 44 };
int i, j;
for(i = 0; i < 2; i++) { /* for all the rows... */
for(j = 0; j < 5; j++) { /* print all the columns! */
int index = i*5 + j; /* calc the index */
printf("%d ", a[index]);
}
/* at the end of the row, print a newline for the next row */
printf("\n");
}
return 0;
}
So in the middle of the loop we've declared a local variable index (yes, you can do that--remember local variables are local to their block (that is, local to their surrounding squirrley braces)) and we calculate it using i and j. Look at that calculation for a bit to make sure it's correct. This is technically what the compiler does behind your back when you accessed the array using multidimensional notation.
Sometimes you have a type and you want it to be a different type. Here's a great example:
int main(void)
{
int a = 5;
int b = 10;
float f;
f = a / b; /* calculate 5 divided by 10 */
printf("%.2f\n", f);
return 0;
}
And this prints:
0
...What? Five divided by 10 is zero? Since when? I'll tell you:
since we entered the world of integer-only division. When you divide
one
Turns out, either integer (or both) in the divide can be made into a
"Get on with it! How do you cast?" Oh yeah--I guess I should actually do it. You might recall the cast from other parts of this guide, but just in case, we'll show it again:
f = (float)a / b; /* calculate 5 divided by 10 */
Bam! There is is! Putting the new type in parens in front of the expression to be converted, and it magically becomes that type!
You can cast almost anything to almost anything else, and if you mess it up somehow, it's entirely your fault since the compiler will blindly do whatever you ask. :-)
This topic is a little bit more advanced, but bear with it for a bit.
An incomplete type is simply the declaration of the name of a particular
For example, here we use a pointer to a type without actually having it defined anywhere in main(). (It is defined elsewhere, though.)
struct foo; /* incomplete type! Notice it's, well, incomplete. */
int main(void)
{
struct foo *w;
w = get_next_wombat(); /* grab a wombat */
process_wombat(w); /* use it somewhere */
return 0;
}
I'm telling you this in case you find yourself trying to include a
header that includes another header that includes the same header, or if
your builds are taking forever because you're including too many
headers, or...more likely you'll see an error along the lines of "cannot
reference incomplete type". This error means you've tried to do too
much with the incomplete type (like you tried to dereference it or use a
field in it), and you need to #include the right
header file with the full complete declaration of the
Welcome to THE VOID! As Neo Anderson would say,
"...Whoa." What is this
Stop! Before you get confused, a
A
Yes. Yes, it does. Because of that, you cannot dereference a
How on Valhalla is this going to be of any use then? Why would you even want a pointer you didn't know the type of?
The Specification: Write a function that can append pointers of any type to an array. Also write a function that can return a particular pointer for a certain index.
So in this case, we're going to write a couple useful little
functions for storing off pointers, and returning them later. The
function has be to type-agnostic, that is, it must be able to
store pointers of any type. This is something of a fairly common
feature to libraries of code that manipulate data--lots of them take
Normally, we'd write a linked list or something to hold these, but that's outside the scope of this book. So we'll just use an array, instead, in this superficial example. Hmmm. Maybe I should write a beginning data structures book...
Anyway, the specification calls for two functions, so let's pound those puppies out right here:
#include <stdio.h>
void *pointer_array[10]; /* we can hold up to 10 void-pointers */
int index=0;
void append_pointer(void *p)
{
pointer_array[index++] = p;
}
void *get_pointer(int i)
{
return pointer_array[i];
}
Since we need to store those pointers somewhere, I went ahead and made a global array of them that can be accessed from within both functions. Also, I made a global index variable to remember where to store the next appended pointer in the array.
So check the code out for append_pointer() there. How is all that crammed together into one line? Well, we need to do two things when we append: store the data at the current index, and move the index to the next available spot. We copy the data using the assignment operator, and then notice that we use the post-increment operator (++) to increment the index. Remember what post-increment means? It means the increment is done after the rest of the expression is evaluated, including that assignment.
The other function, get_pointer, simply returns the
Finally, we have that code all written--now how can we actually use it? Let's write a main() function that will use these functions:
int main(void)
{
char *s = "some data!"; /* s points to a constant string (char*) */
int a = 10;
int *b;
char *s2; /* when we call get_pointer(), we'll store them back here */
int *b2;
b = &a; /* b is a pointer to a */
/* now let's store them both, even though they're different types */
append_pointer(s);
append_pointer(b);
/* they're stored! let's get them back! */
s2 = get_pointer(0); /* this was at index 0 */
b2 = get_pointer(1); /* this was at index 1 */
return 0;
}
See how the pointer types are interchangable through the
I think I have just enough time before the plane lands to talk about
int main(void)
{
int *p = NULL;
if (p == NULL) {
printf("p is uninitialized!\n");
} else {
printf("p points to %d\n", *p);
}
return 0;
}
Note that pointers aren't preinitialized to
Modern technology has landed me safely here at LAX, and I'm free to continue writing while I wait for my plane to Ireland. Tell me you're not jealous at least on some level, and I won't believe you.
But enough about me; let's talk about programming. (How's that for a geek pick-up line? If you use it, do me a favor and don't credit me.)
You've already seen how you can use the
So what about if you declare something as
You'll find that your bigger projects little bite-sized pieces themselves fit into larger bite-sized pieces (just like that picture of the little fish getting eaten by the larger fish being eaten by the fish that's larger, still.) When you have enough related smaller bite-sized pieces, it often makes sense to put them in their own source file.
I'm going to rip the example from the section on
One of the many issues with that example program (there are all kinds of shortcomings and bugs in it) is that we've declared a global variable called index. Now, "index" is a pretty common word, and it's entirely likely that somewhere else in the project, someone will make up their own variable and name it the same thing as yours. This could cause all kinds of problems, not the least of which is they can modify your value of index, something that is very important to you.
One solution is to put all your stuff in one source file, and then
declare index to be
So here is a quick rewrite of the code to be stuck it its own file:
/** file parray.c **/
static void *pointer_array[10]; /* now no one can see it except this file! */
static int index=0; /* same for this one! */
/* but these functions are NOT static, */
/* so they can be used from other files: */
void append_pointer(void *p)
{
pointer_array[index++] = p;
}
void *get_pointer(int i)
{
return pointer_array[i];
}
/** end of file parray.c **/
What would be proper at this point would be to make a file called parray.h that has the function prototypes for the two functions in it. Then the file that has main() in it can #include parray.h and use the functions when it is all linked together.
Like I'm so fond of saying, projects generally grow too big for a single file, very similarly to how The Blob grew to be enormous and had to be defeated by Steve McQueen. Unfortunately, McQueen has already made his great escape to Heaven, and isn't here to help you with your code. Sorry.
So when you split projects up, you should try to do it in bite-sized modules that make sense to have in individual files. For instance, all the code responsible for calculating Fast Fourier Transforms (a mathematical construct, for those not in the know), would be a good candidate for its own source file. Or maybe all the code that controls a particular AI bot for a game could be in its own file. It's more of a guideline than a rule, but if something's not a least in some way related to what you have in a particular source file already, maybe it should go elsewhere. A perfect illustrative question for this scenario might be, "What is the 3D rendering code doing in the middle of the sound driver code?"
When you do move code to its own source file, there is almost always a header file that you should write to go along with it. The code in the new source file (which will be a bunch of functions) will need to have prototypes and other things made visible to the other files so they can be used. The way the other source files use other files is to $#include their header files.
So for example, let's make a small set of functions and stick them in a file called simplemath.c:
/** file simplemath.c **/
int plusone(int a)
{
return a+1;
}
int minusone(int a)
{
return a-1;
}
/** end of file simplemath.c **/
A couple simple functions, there. Nothing too complex, yes? But by themselves, they're not much use, and for other people to use them we need to distribute their prototypes in a header file. Get ready to absorbe this...you should recognize the prototypes in there, but I've added some new stuff:
/** file simplemath.h **/ #ifndef _SIMPLEMATH_H_ #define _SIMPLEMATH_H_ /* here are the prototypes: */ int plusone(int a); int minusone(int a); #endif /** end of file simplemath.h **/
Icky. What is all that #ifndef stuff? And the #define and the #endif? They are boilerplate code (that is, code that is more often than not stuck in a file) that prevents the header file from being included multiple times.
The short version of the logic is this: if the symbol _SIMPLEMATH_H_ isn't defined then define it, and then do all the normal header stuff. Else if the symbol _SIMPLEMATH_H_ is already defined, do nothing. In this way, the bulk of the header file is included only once for the build, no matter how many other files try to include it. This is a good idea, since at best it's a redundant waste of time to re-include it, and at worst, it can cause compile-time errors.
Well, we have the header and the source files, and now it's time to write a file with main() in it so that we can actually use these things:
/** file main.c **/
#include "simplemath.h"
int main(void)
{
int a = 10, b;
b = plusone(a); /* make that processor work! */
return 0;
}
/** end of file main.c **/
Check it out! We used double-quotes in the #include instead of angle brackets! What this tells the preprocessor to do is, "include this file from the current directory instead of the standard system directory." I'm assuming that you're putting all these files in the same place for the purposes of this exercise.
Recall that including a file is exactly like bringing it into your source at that point, so, as such, we bring the prototypes into the source right there, and then the functions are free to be used in main(). Huzzah!
One last question! How do we actually build this whole thing? From the command line:
$ cc -o main main.c simplemath.c
You can lump all the sources on the command line, and it'll build them together, nice and easy.
Remember back about a million years ago when you first started reading this guide and I mentioned something about spinach? That's right--you remember how spinach relates to the whole computing process?
Of course you don't remember. I just made it up just now; I've never mentioned spinach in this guide. I mean, c'mon. What does spinach have to do with anything? Sheesh!
Let me steer you to a less leafy topic: the C preprocessor. As the name suggests, this little program will process source code before the C compiler sees it. This gives you a little bit more control over what gets compiled and how it gets compiled.
You've already seen one of the most common preprocessor directives: #include. Other sections of the guide have touched upon various other ones, but we'll lay them all out here for fun.
The well-known #include directive pulls in source from another file. This other file should be a header file in virtually every single case.
On each system, there are a number of standard include files that you can use for various tasks. Most popularly, you've seen stdio.h used. How did the system know where to find it? Well, each compiler has a set of directories it looks in for header files when you specify the file name in angle brackets. (On Unix systems, it commonly searches the /usr/include directory.)
If you want to include a file from the same directory as the source, use double quotes around the name of the file. Some examples:
/* include from the system include directory: */ #include <stdio.h> #include <sys/types.h> /* include from the local directory: */ #include "mutants.h" #include "fishies/halibut.h"
As you can see from the example, you can also specify a relative path into subdirectories out of the main directory. (Under Unix, again, there is a file called types.h in the directory /usr/include/sys.)
The #define is one of the more powerful C preprocessor directives. With it you can declare constants to be substituted into the source code before the compiler even sees them. Let's say you have a lot of constants scattered all over your program and each number is hard-coded (that is, the number is written explicitly in the code, like "2").
Now, you thought it was a constant when you wrote it because the people you got the specification swore to you up and down on pain of torture that the number would be "2", and it would never change in 100 million years so strike them blind right now.
Hey--sounds good. You even have someone to blame if it did change, and it probably won't anyway since they seem so sure.
Don't be a fool.
The spec will change, and it will do so right after you have put the number "2" in approximately three hundred thousand places throughout your source, they're going to say, "You know what? Two just isn't going to cut it--we need three. Is that a hard change to make?"
Blame or not, you're going to be the one that has to change it. A good programmer will realize that hard-coding numbers like this isn't a good idea, and one way to get around it is to use a #define directive. Check out this example:
#define PI 3.14159265358979 /* more pi than you can handle */
int main(void)
{
float r =10.0;
printf("pi: %f\n", PI);
printf("pi/2: %f\n", PI/2);
printf("area: %f\n", PI*r*r);
printf("circumference: %f\n", 2*PI*r);
return 0;
}
(Typically #defines are all capitals, by convention.) So, hey, we just printed that thing out as if it was a float. Well, it is a float. Remember--the C preprocessor substitutes the value of PI before the compiler even sees it. It is just as if you had typed it there yourself.
Now let's say you've used PI all over the place and now you're just about ready to ship, and the designers come to you and say, "So, this whole pi thing, um, we're thinking it needs to be four instead of three-point-one-whatever. Is that a hard change?"
No problem in this case, no matter how deranged the change request. All you have to do is change the one #define at the top, and it's therefore automatically changed all over the code when the C preprocessor runs through it:
#define PI 4.0 /* whatever you say, boss */
Pretty cool, huh. Well, it's perhaps not as good as to be "cool", but you have not yet witnessed the destructive power of this fully operational preprocessor directive! You can actually use #define to write little macros that are like miniature functions that the preprocessor evaluates, again, before the C compiler sees the code. To make a macro like this, you give an argument list (without types, because the preprocessor knows nothing about types, and then you list how that is to be used. For instance, if we want a macro that evaluates to a number you pass it times 3490, we could do the following:
#define TIMES3490(x) ((x)*3490) /* no semicolon, notice! */
void evaluate_fruit(void)
{
printf("40 * 3490 = %d\n", TIMES3490(40));
}
In that example, the preprocessor will take the macro and expand it so that it looks like this to the compiler:
void evaluate_fruit(void)
{
printf("40 * 3490 = %d\n", ((40)*3490));
}
(Actually the preprocessor can do basic math, so it'll probably reduce it directly to "139600". But this is my example and I'll do as I please!)
Now here's a question for you: are you taking it on blind faith that you need all those parenthesis in the macro, like you're some kind of LISP superhero, or are you wondering, "Why am I wasting precious moments of my life to enter all these parens?"
Well, you should be wondering! It turns out there are cases where you can really generate some evil code with a macro without realizing it. Take a look at this example which builds without a problem:
#define TIMES3490(x) x*3490
void walrus_discovery_unit(void)
{
int tuskcount = 7;
printf("(tuskcount+2) * 3490 = %d\n", TIMES3490(tuskcount + 2));
}
What's wrong here? We're calculating tuskcount+2, and then passing that through the TIMES3490() macro. But look at what it expands to:
printf("(tuskcount+2) * 3490 = %d\n", tuskcount+2*3490);
Instead of calculating (tuskcount+2)*3490, we're calculating tuskcount+(2*3490), because multiplication comes before addition in the order of operations! See, adding all those extra parens to the macro prevents this sort of thing from happening. So programmers with good practices will automatically put a set of parens around each usage of the parameter variable in the macro, as well as a set of parens around the outside of the macro itself.
There are some conditionals that the C preprocessor can use to discard blocks of code so that the compiler never sees them. The #if directive operates like the C if, and you can pass it an expression to be evaluated. It is most common used to block off huge chunks of code like a comment, when you don't want it to get built:
void set_hamster_speed(int warpfactor)
{
#if 0
uh this code isn't written yet. someone should really write it.
#endif
}
You can't nest comments in C, but you can nest #if directives all you want, so it can be very helpful for that.
The other if-statement, #ifdef is true if the subsequent macro is already defined. There's a negative version of this directive called #ifndef ("if not defined"). #ifndef is very commonly used with header files to keep them from being included multiple times:
/** file aardvark.h **/ #ifndef _AARDVARK_H_ #define _AARDVARK_H_ int get_nose_length(void); void set_nose_length(int len); #endif /** end of file aardvark.h **/
The first time this file is included, _AARDVARK_H_ is not yet defined, so it goes to the next line, and defines it, and then does some function prototypes, and you'll see there at the end, the whole #if-type directive is culminated with an #endif statement. Now if the file is included again (which can happen when you have a lot of header files are including other header files ad infininininini--cough!), the macro _AARDVARK_H_ will already be defined, and so the #ifndef will fail, and the file up to the #endif will be discarded by the preprocessor.
Another extremely useful thing to do here is to have certain code compile for a certain platform, and have other code compile for a different platform. Lots of people like to build software with a macro defined for the type of platform they're on, such as LINUX or WIN32. And you can use this to great effect so that your code will compile and work on different types of systems:
void run_command_shell(void)
{
#ifdef WIN32
system("COMMAND.EXE");
#elifdef LINUX
system("/bin/bash");
#else
#error We don't have no steenkin shells!
#endif
}
A couple new things there, most notable #elifdef. This is the contraction of "else ifdef", which must be used in that case. If you're using #if, then you'd use the corresponding #elif.
Also I threw in an #error directive, there. This will cause the preprocessor to bomb out right at that point with the given message.
You've already seen how you can have a pointer to a variable...and you've already seen how a pointer is a variable, so is it possible to have a pointer to a pointer?
No, it's not.
I'm kidding--of course it's possible. Would I have this section of the guide if it wasn't?
There are a few good reasons why we'd want to have a pointer to a pointer, and we'll give you the simple one first: you want to pass a pointer as a parameter to a function, have the function modify it, and have the results reflected back to the caller.
Note that this is exactly the reason why we use pointers in function calls in the first place: we want the function to be able to modify the thing the pointer points to. In this case, though, the thing we want it to modify is another pointer. For example:
void get_string(int a, char **s)
{
switch(a) {
case 0:
*s = "everybody";
break;
case 1:
*s = "was";
break;
case 2:
*s = "kung-foo fighting";
break;
default:
*s = "errrrrrnt!";
}
}
int main(void)
{
char *s;
get_string(2, &s);
printf("s is \"%s\"\n", s); /* 's is "kung-foo fighting"' */
return 0;
}
What we have, above, is some code that will deliver a string (pointer
to a
There's really nothing mysterious here. You have a pointer to a thing, so you can dereference the pointer to change the thing. It's just like before, except for that fact that we're operating on a pointer now instead of just a plain base type.
What else can we do with pointers to pointers? You can dynamically
make a construction similar to a two-dimensional array with them. The
following example relies on your knowledge that the function call
malloc() returns a chunk of sequential bytes of memory that
you can use as you will. In this case, we'll use them to create a
number of
int main(void)
{
char **p;
p = malloc(sizeof(char*) * 10); // allocate 10 char*s
return 0;
}
Swell. Now what can we do with those? Well, they don't point to anything yet, but we can call malloc() for each of them in turn and then we'll have a big block of memory we can store strings in.
int main(void)
{
char **p;
int i;
p = malloc(sizeof(char*) * 10); // allocate 10 char*s-worth of bytes
for(i = 0; i < 10; i++) {
*(p+i) = malloc(30); // 30 bytes for each pointer
// alternatively we could have written, above:
// p[i] = malloc(30);
// but we didn't.
sprintf(*(p+i), "this is string #%d", i);
}
for(i = 0; i < 10; i++) {
printf("%d: %s\n", i, p[i]); // p[i] same as *(p+i)
}
return 0;
}
Ok, as you're probably thinking, this is where things get completely wacko-jacko. Let's look at that second malloc() line and dissect it one piece at a time.
You know that p is a pointer to a pointer to a
And we know this
And what do we do with that
And what do we use that memory for (sheesh, this could go on forever!)--well, we use a variant of printf() called sprintf() that writes the result into a string instead of to the console.
And there you have it. Finally, for fun, we print out the results using array notation to access the strings instead of pointer arithmetic.
You've completely mastered all that pointer stuff, right? I mean, you are the Pointer Master! No, really, I insist!
So, with that in mind, we're going to take the whole pointer and address idea to the next phase and learn a little bit about the machine code that the compiler produces. I know this seems like it has nothing to do with this section, Pointers to Functions, but it's background that will only make you stronger. (Provided, that is, it doesn't kill you first. Admittedly, the chances of death from trying to understand this section are slim, but you might want to read it in a padded room just as a precautionary measure.)
Long ago I mentioned that the compiler takes your C source code and produces machine code that the processor can execute. These machine code instructions are small (taking between one and four bytes or memory, typically, with optionally up to, say, 32 bytes of arguments per instruction--these numbers vary depending on the processor in question). This isn't so important as the fact that these instructions have to be stored somewhere. Guess where.
You thought that was a rhetorical command, but no, I really do want you to guess where, generally, the instructions are stored.
You have your guess? Good. Is it animal, vegetable, or mineral? Can you fly in it? Is it a rocketship? Yay!
But, cerebral digression aside, yes, you are correct, the instructions are stored in memory, just like variables are stored in memory. Instructions themselves have addresses, and a special variable in the CPU (generally known as a "register" in CPU-lingo) points to the address of the currently executing instruction.
Whatwhat? I said "points to" and "address-of"! Suddenly we have a connection back to pointers and all that...familiar ground again. But what did I just say? I said: instructions are held in addresses, and, therefore, you have have a pointer to a block of instructions. A block of instructions in C is held in a function, and, therefore, you can have a pointer to a function. Voila!
Ok, so if you have a function, how do you get the address of the function? Yes, you can use the &, but most people don't. It's similar to the situation with arrays, where the name of the array without square brackets is a pointer to the first element in the array; the name of the function without parens is a pointer to the first instruction in the function. That's the easy part.
The hard part is declaring a variable to be of type "pointer to function". It's hard because the syntax is funky:
// declare p as a pointer to a function that takes two int // parameters, and returns a float: float (*p)(int, int);
Again, note that this is a declaration of a pointer to a function. It doesn't yet point to anything in particular. Also notice that you don't have to put dummy parameter names in the declaration of the pointer variable. All right, let's make a function, point to it, and call it:
int deliver_fruit(char *address, float speed)
{
printf("Delivering fruit to %s at speed %.2f\n", address, speed);
return 3490;
}
int main(void)
{
int (*p)(char*,float); // declare a function pointer variable
p = deliver_fruit; // p now points to the deliver_fruit() function
deliver_fruit("My house", 5280.0); // a normal call
p("My house", 5280.0); // the same call, but using the pointer
return 0;
}
What the heck good is this? The usual reasons are these:
For example, long ago a friend of mine and I wrote a program that
would simulate a bunch of creatures running around a grid. The
creatures each had a
struct creature {
int xpos;
int ypos;
float health;
int (*behavior)(struct useful_data*);
};
So for each round of the simulation, we'd walk through the list of creatures and call their behavior function (passing a pointer to a bunch of useful data so the function could see other creatures, know about itself, etc.) In this way, it was easy to code bugs up as having different behaviors.
Indeed, I wrote a creature called a "brainwasher" that would, when it got close to another creature, change that creature's behavior pointer to point to the brainwasher's behavior code! Of course, it didn't take long before they were all brainwashers, and then starved and cannibalized themselves to death. Let that be a lesson to you.
Ever wonder, in your spare time, while you lay awake at night thinking about the C Programming Language, how functions like printf() and scanf() seem to take an arbitrary number of arguments and other functions take a specific number? How do you even write a function prototype for a function that takes a variable number of arguments?
(Don't get confused over terminology here--we're not talking about variables. In this case, "variable" retains its usual boring old meaning of "an arbitrary number of".)
Well, there are some little tricks that have been set up for you in this case. Remember how all the arguments are pushed onto the stack when passed to a function? Well, some macros have been set up to help you walk along the stack and pull arguments off one at a time. In this way, you don't need to know at compile-time what the argument list will look like--you just need to know how to parse it.
For instance, let's write a function that averages an arbitrary number of positive numbers. We can pull numbers off the stack one at a time and average them all, but we need to know when to stop pulling stuff off the stack. One way to do this is to have a sentinel value that you watch for--when you hit it, you stop. Another way is to put some kind of information in the mandatory first argument. Let's do option B and put the count of the number of arguments to be averaged in as the first argument to the function.
Here's the prototype for the function--this is how we declare a variable argument list. The first argument (at least) must be specified, but that's all:
float average(int count, ...);
It's the magical "..." that does it, see? This lets the compiler know that there can be more arguments after the first one, but doesn'r require you to say what they are. So this is how we are able to pass many or few (but at least one, the first argument) arguments to the function.
But if we don't have names for the variables specified in the function header, how do we use them in the function? Well, aren't we the demanding ones, actually wanting to use our function! Ok, I'll tell you!
There is a special type declared in the header stdarg.h
called
We operate on our
So an example! Let's write that average() function. Remember: va_start(), va_arg(), va_arg(), va_arg(), etc., and then va_end()!
float average(int count, ...)
{
float ave = 0;
int i;
va_list args; // here's our va_list!
va_start(args, count); // tell it the stack starts with "count"
// inside the while(), pull int args off the stack:
for(i = 0; i < count; i++) {
int val = va_arg(args, int); // get next int argument
ave += (float)val; // cast the value to a float and add to total
}
va_end(args); // clean this up
return ave / count; // calc and return the average
}
So there you have it. As you see, the va_arg() macro
pulls the next argument off the stack of the given type. So you have to
know in advance what type the thing is. We know for our
average() function, all the types are
Well, if you'll notice, this is exactly what our old friend printf() does! It knows what type to call va_arg() with, since it says so right in the format string.
There are a number of functions that helpfully accept a
Assignment: Implement a version of printf() called timestamp_printf() that works exactly like printf() except it prints the time followed by a newline followed by the data output specified by the printf()-style format string.
Holy cow! At first glance, it looks like you're going to have to implement a clone of printf() just to get a timestamp out in front of it! And printf() is, as we say in the industry, "nontrivial"! See you next year!
Wait, though--wait, wait...there must be a way to do it easily, or this author is complete insane to give you this assignment, and that couldn't be. Fruit! Where is my cheese!? Blalalauugh!!
Ahem. I'm all right, really, Your Honor. I'm looking into my crystal
ball and I'm seeing...a type
Welcome to the world of vprintf()! It does exactly that, by Jove! Here's a lovely prototype:
int vprintf(const char *format, va_list args);
All righty, so what building blocks do we need for this assignment? The spec says we need to do something just like printf(), so our function, like printf() is going to accept a format string first, followed by a variable number of arguments, something like this:
int timestamp_printf(char *format, ...);
But before it prints its stuff, it needs to output a timestamp followed by a newline. The exact format of the timestamp wasn't specified in the assignment, so I'm going to assume something in the form of "weekday month day hh:mm:ss year". By amazing coincidence, a function exists called ctime() that returns a string in exactly that format, given the current system time.
So the plan is to print a timestamp, then take our variable argument
list, run it through va_start to get a
#include <stdio.h>
#include <stdarg.h>
#include <time.h> // for time() and ctime();
int timestamp_printf(char *format, ...)
{
va_list args;
time_t system_time;
char *timestr;
int return_value;
system_time = time(NULL); // system time in seconds since epoch
timestr = ctime(&system_time); // ready-to-print timestamp
// print the timestamp:
printf("%s", timestr); // timestr has a newline on the end already
// get our va_list:
va_start(args, format);
// call vprintf() with our arg list:
return_value = vprintf(format, args);
// done with list, so we have to call va_end():
va_end(args);
// since we want to be *exactly* like printf(), we have saved its
// return value, and we'll pass it on right here:
return return_value;
}
int main(void)
{
// example call:
timestamp_printf("Brought to you by the number %d\n", 3490);
return 0;
}
And there you have it! Your own little printf()-like functionality!
Now, not every function has a "v" in front of the name for processing variable argument lists, but most notably all the variants of printf() and scanf() do, so feel free to use them as you see fit!
TODO order of operations, arrays of pointers to functions
| << Prev | Beej's Guide to C | Next >> |