metis
metis

Reputation: 1044

pointers, conversion of char ** to char *

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

Answers (5)

Cheers and hth. - Alf
Cheers and hth. - Alf

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

Bob
Bob

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

R Sahu
R Sahu

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 == &notes[3]. That leads to the line

cout << *(char **)elemAddr;

to be the same as

cout << notes[3];

Upvotes: 5

Mats Petersson
Mats Petersson

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

kamshi
kamshi

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

&notes[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

Related Questions