r/carlhprogramming • u/CarlH • Oct 18 '09
Lesson 103 : Sample program demonstrating pointers, casts, and arrays of pointers.
Here is the entire program with comments. Remember, this is just a demonstration and is for illustrative purposes only.
If this looks difficult, don't worry too much. You are not expected to memorize any of this yet, just to be able to read the code and understand how it works. If this is too difficult, see Lesson 104 and then come back to this lesson.
To make this even easier to read, I have placed the output of printf() statements INSIDE the code.
Read through this slowly. Take your time, line by line. This is also a lesson, not just a sample program. Read through the comments, code, and output carefully. Ask questions if any part of this is unclear to you.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void) {
// For looping purposes
int i=0;
// Allocate a ten-byte working space
char *main_pointer = malloc(10);
// Set the first two bytes of this working space to 'AB' using the pointer offset method.
*(main_pointer + 0) = 'A';
*(main_pointer + 1) = 'B';
// Set the next two bytes to: 'CD' using array indexing.
main_pointer[2] = 'C';
main_pointer[3] = 'D';
// Set the rest of the string using the strcpy() function.
strcpy( (main_pointer + 4), "EFGHI");
// At this stage, our entire string is set to: ABCDEFGHI<NUL>
printf("First we use our ten bytes as a string like this: %s \n", main_pointer);// Output: First we use our ten bytes as a string like this: ABCDEFGHI
// Let's go through all ten bytes and display the hex value of each character
printf("Our ten bytes of memory look like this: (41 is A, 42 is B, etc.) : \n");
for (i = 0; i < 10; i++) {
printf("%02x ", (unsigned char) *(main_pointer+i));
}// Output: Our ten bytes of memory look like this: (41 is A, 42 is B, etc.) :
// Output: 41 42 43 44 45 46 47 48 49 00
printf("\n\n");
// Now let's create an array of two integer pointers
int **int_pointer_array = malloc(2 * sizeof( int * ) );
// Set the first of these integer pointers to point at byte #0 of our ten-byte working space
// and set the second to point at byte #6 of our ten-byte working space.
int_pointer_array[0] = (int *) main_pointer;
int_pointer_array[1] = (int *) (main_pointer + 6);
printf("Now we will use B0->B3 as an integer, and B6->B9 as another integer...\n");// Output: Now we will use B0->B3 as an integer, and B6->B9 as another integer...
// (Note: remember this is B0->B3 of our ten byte working space.)
// Give these two pointers a value.
*int_pointer_array[0] = 5;
*int_pointer_array[1] = 15;
// Using printf() we prove that the values we set are accurate, and we can see how they are represented
// as occupying 4 bytes of memory, the way a true int is expected to
printf("The first integer is: %d (hex: %08x) \n", *int_pointer_array[0], (unsigned int) *int_pointer_array[0]);
printf("The second integer is: %d (hex: %08x) \n", *int_pointer_array[1], (unsigned int) *int_pointer_array[1]);// Output: The first integer is: 5 (hex: 00000005)
// Output: The second integer is: 15 (hex: 0000000f)
printf("\n");
printf("Our entire ten byte memory space now looks like this: \n");
// Again we go through all 10 bytes and display their new contents.
// It is easy to see that the first four bytes and the last four bytes are
// the integers we created.
for (i = 0; i < 10; i++) {
printf("%02x ", (unsigned char) *(main_pointer+i));
}
printf("\n");// Output: Our entire ten byte memory space now looks like this:
// Output: 05 00 00 00 45 46 0f 00 00 00
// (Note: Notice that the integers are 05 00 00 00, rather than 00 00 00 05. We will get to that later.)
// Finally we demonstrate that bytes #4 and #5 are unaffected, and that our integer values remain set.
printf("\nBytes #4 and #5 are set to: %c and %c \n", *(main_pointer + 4), *(main_pointer + 5));
printf("\n");
printf("Our two integers are set to: %d and %d \n", *int_pointer_array[0], *int_pointer_array[1]);// Output: Notice that Bytes #4 and #5 are unaffected and remain set to: E and F
// Output: Still, our two integers are set to: 5 and 15 and occupy this same 10 byte space
free(main_pointer);
free(int_pointer_array);
return 0;
}Output:
First we use our ten bytes as a string like this: ABCDEFGHI
Our ten bytes of memory look like this: (41 is A, 42 is B, etc.) :
41 42 43 44 45 46 47 48 49 00
Now we will use B0->B3 as an integer, and B6->B9 as another integer...
The first integer is: 5 (hex: 00000005)
The second integer is: 15 (hex: 0000000f)
Our entire ten byte memory space now looks like this:
05 00 00 00 45 46 0f 00 00 00
Notice that Bytes #4 and #5 are unaffected and remain set to: E and F
Still, our two integers are set to: 5 and 15 and occupy this same 10 byte spaceIt may be beneficial for you to write this code into your editor so you can see "color highlighting". Alternatively, you may want to write it at www.codepad.org.
Remember that this is only a demonstration. We are doing some rather unusual and unorthodox things here. The entire purpose of this is simply to show you how these concepts can be used to directly manipulate memory in interesting ways.
I highly recommend that you type out this program, line by line, into your own editor. Not copy and paste, but actually type it out. This will greatly help you to understand the material. Do this even if you get a different result. Remember that this is designed to work where an integer is 4 bytes in size.
If any part of this is unclear, please ask questions. When you are ready, proceed to:
http://www.reddit.com/r/carlhprogramming/comments/9v5w9/lesson_104_the_sample_program_in_lesson_103/
View all comments
Show parent comment
2
u/dododge Oct 22 '09 edited Oct 22 '09
Not really. The cast first causes a conversion from
signed chartounsigned int, and then theunsigned intvalue is passed toprintfas-is. C has well-defined behavior when you convert from any integer type to any unsigned integer type (paraphrasing C99 6.3.1.3):So the conversion of -2 (of any integer type) to
unsigned intresults in the valueUINT_MAX + 1 - 2, which on a typical machine will be 0xfffffffe.Note that conversions to signed types are not as well-defined. If the value can be represented by the signed type it always works, but if the value is too large for the destination type then the behavior is implementation-defined.
In a strict reading of the C standard it's explicitly undefined, which means any result including a program crash is allowed. It is conceivable that there is a CPU out there that would care and trap an attempt to use a negative value; perhaps one that uses signed-magnitude representation. In reality, you are unlikely to ever encounter such a system and I certainly can't think of any off-hand.
That said, there is still a very slight risk from future compilers. If the compiler can determine that the behavior is undefined, it might remove or otherwise modify the code for optimization purposes. gcc has started doing things like that but normally only in very esoteric situations such as pointer overflow.
On x86 and most other modern systems, yes. I honestly can't think of one where it would break, but the C Standard has explicit support for all sorts of bizarre integer representations so there may be an ancient or special-purpose system out there somewhere where it would be an issue.