Reputation: 1044
I am studying pointers, but I have been stumped by the example program below. It is supposed to be doing a conversion of char**
to char*
, but I don't understand the logic behind the program. What is the program doing?
#include <iostream>
using namespace std;
int main() {
char *notes[] = {"cpp","python","java","mariadb"};
void * base = notes; // notes and base, holds the address of note's first element
void * elemAddr = (char*) base + 3* sizeof(char *); // i didn't understand this line???
cout << *(char **)elemAddr; // and this line
return 0;
}
Upvotes: 4
Views: 157
Reputation: 145239
Apparently the example code is meant to illustrate what goes on under the hood when you use array indexing.
Repeating the code (as it was when I wrote this):
#include <iostream>
using namespace std;
int main() {
char *notes[] = {"cpp","python","java","mariadb"};
void * base = notes; // notes and base, holds the address of note's first element
void * elemAddr = (char*) base + 3* sizeof(char *); // i didn't understand this line???
cout << *(char **)elemAddr; // and this line
return 0;
}
First, the declaration
char *notes[] = {"cpp","python","java","mariadb"};
declares an array of pointers to char
. Each pointer is initialized with a string literal. This language feature was deprecated in the original C++ standard, C++98, and was finally removed in C++11, so that with modern C++ (as of this writing C++14) it's just invalid code, code that will not compile with a conforming compiler.
In standard C++ it could be
char const *notes[] = {"cpp","python","java","mariadb"};
But let's ignore the const
issue, and assume C++03 or C++98.
Then the declaration
void * base = notes;
declares a void*
pointer called base
, initialized to the address of the first item of the array notes
. This works via array expression decay, where an expression referring to an array produces a pointer to its first item.
The declaration
void * elemAddr = (char*) base + 3* sizeof(char *);
is evidently intended to illustrate what's going on behind the scenes for the [3]
indexing in
auto p = & notes[3];
This works via byte oriented address arithmetic (char
and its variants is the C++ notion of smallest addressable unit, a.k.a. byte). Starting with the base address of the array, one adds 3 times the size of each item. This lands you on the start of the 3'rd item.
Finally, the expression
*(char **)elemAddr
uses that item. It's just ugly due to using low level types. But essentially, the item is a char*
and so the address of the item is casted to char**
, and then that pointer is dereferenced, yielding the char*
pointer itself, which is the result of the expression (and passed to cout
).
Upvotes: 2
Reputation: 1851
Let's go line by line: 1st Line: We create a pointer to an array of strings... this is a pointer to the base address of an array... of pointers.
2nd line: We make our char* into a void*, this this doesn't change the value of the pointer at all, just the type associated with it by the compiler.
3rd line: When we operate on the base pointer, we cast it to a char* essentially undoing what we did on line 2. We add 3 * the sizeof( char*) to our pointer. This makes the pointer that was pointing to the base of our array of pointers, now point to the 4th pointer in our array.
4th line: now we double deference our pointer, what is our pointer pointing to? The third element in an array of pointers. What is that pointer pointing to? The string created back on line one.
Upvotes: 0
Reputation: 206567
These lines:
char *notes[] = {"cpp","python","java","mariadb"};
void * base = notes;
void * elemAddr = (char*) base + 3* sizeof(char *);
cout << *(char **)elemAddr;
are an obfuscated equivalent of:
char *notes[] = {"cpp","python","java","mariadb"};
cout << notes[3];
Explanation:
void * base = notes;
void * elemAddr = (char*) base + 3* sizeof(char *);
is the same as:
char * base = (char*)notes;
char * elemAddr = base + 3 * sizeof(char *);
Since pointers are usually of the same size, those lines are kind of the same as:
char ** base = notes;
char ** elemAddr = base + 3;
which makes elemAddr == ¬es[3]
. That leads to the line
cout << *(char **)elemAddr;
to be the same as
cout << notes[3];
Upvotes: 5
Reputation: 129314
Ok, I'll bite:
char *notes[] = {"cpp","python","java","mariadb"};
Declares an array of pointers to char *
. (should actually be const char *notes[]
since we can't modify the content ever)
void * base = notes; // notes and base, holds the address of note's first element
So assigns the address of the array notes
to base
, and losing any type information in the process.
void * elemAddr = (char*) base + 3* sizeof(char *); // i didn't understand this line???
Cast base
to char *
, meaning each element is now sizeof(char) == 1
. Add 3 * sizeof(char *)
to that pointer -> 3 elements into the notes
array, and assign it back to elemAddr
.
cout << *(char **)elemAddr; // and this line
Since elemAddr
is pointing to an element in notes
, which is a char*
, it really is a pointer to a pointer to char
, and we want to print what it point to, hence the *
at the very beginning.
It is not very readable, and it would be MUCH simpler to write
const char* notes[] = { ... };
cout << notes[3];
but then you wouldn't have posted here...
Upvotes: 2
Reputation: 615
The third line says: "Convert base to a char pointer, and add 3 times the size of an char pointer to the result. Then, implicitly cast the result to a void pointer and store it in elemAddr"
In the end, elemAddr points to the address of "mariadb". The cast to (char*) isn't necessary to do the pointer arithmetic, but it prevents the compiler from printing a warning. It is equivalent to
¬es[3]
The fourth line tells the compiler to interpret the pointer as an pointer to char pointers. This pointer is then dereferenced so that the data pointed to by elemAddr is treated as an char pointer, which will be interpreted by std::cout as a C-string.
I don't know where you got this example from, but this type of programming is a honey pot for really horrible bugs and hard to debug code.
Upvotes: 0