main) into a separate file
print_bits.cand then put the declarations of our two functions in a header file:
main, adding code to get information passed in from the operating system, and put that in a separate file
We need to
#include <stdio.h>to use
printf, but we don't need the other C library headers here; they are placed in
print_bits.c. There is a second set of new files (
cast.h) that I'll explain below.
Our project isn't very complicated but it still can benefit from using the make tool. We put a file in the same directory as we're working called
Makefile. Ours has this code:
As explained in the manual for make and Norm Matloff's introduction (here), these instructions consist of
When invoked by itself,
makewill try to build an executable named
bitsby linking 3 object files, these in turn depend on the listed code files. The -c compiler directive says not to do the linking at that stage. And
cleanis self-explanatory. One odd requirement is that after the colon, and on each line that's shifted out, it must be a tab character.
The point is that
makewill call the compiler only if the timestamp of last modification to a .c file shows it's necessary. So in a complex project, only those files that have been changed get re-compiled in each debug cycle.
[UPDATE: As pointed out in comments, there was a problem with an earlier version of this post, because I forgot the proper syntax for our personal header files, which is
#include "print_bits.h"etc. rather than
#include <stdio.h>. The search path is determined by the format of the
#include. See here. My makeshift solution :) was to tell make where to look for our files using
-I., which says to search for them in the current directory. ]
The last part of this little project explores casting. We have two int variables j and k, and a long (int) variable m, declared and assigned in turn (in
cast.c). In order to examine what these look like in memory, we do this:
which assigns the address of j to a char pointer c. We tell the compiler that yes, we really want to do this, using a cast (char *). We examine the surrounding bytes as follows:
We can see a number of things from this example. The pointer to char c (assigned the address of j using &j), when dereferenced, gives decimal 10 in the first byte, followed by three empty bytes. The
sizeofan int on our system is 4 bytes, even though this is a 64-bit executable:
The Intel Core 2 Duo chip addresses memory in "litte-endian" fashion---it's the first byte that holds the value 00001010. And four bytes previous to this is the first byte corresponding to the int k, even though k was assigned after j. (k = 148 + 2*256 = 660). One other thing is that the long int m, whose size is 8 bytes, actually starts 12 bytes before k. I'm not sure why this position was chosen, but I guess it's a "boundary" or something. The fundamental unit of size:
is 8 bytes.
And finally, if we force the compiler to compile a 32-bit executable by using the flag
the last part will print
Now we get 4 byte (32-bit) longs and
size_tis also 4 bytes.
Zipped files on Dropbox (here).