Author Topic: C/C++ question - casting a char * into a char **  (Read 10997 times)

0 Members and 1 Guest are viewing this topic.

Offline sleemanjTopic starter

  • Super Contributor
  • ***
  • Posts: 3024
  • Country: nz
  • Professional tightwad.
    • The electronics hobby components I sell.
C/C++ question - casting a char * into a char **
« on: November 29, 2014, 04:38:15 am »
Given this simplified example, how can I (or indeed, can I) construct a cast that will make the call to fn1() work the same was as it works when I pass it through fn2() to get there?  It looks right to me, but obviously it's not

Code: [Select]
#include <stdio.h>
#include <string.h>

void fn1(const char **arrayOfStrings, int arrayLength)
{
  printf(arrayOfStrings[0]);
}

void fn2(const char *singleString)
{
  fn1(&singleString, 1);
}

int main()
{
   char buffer[12];
   strncpy(buffer, "HELLOWORLD\n", 11);
   
   // This works
   fn2((const char *)buffer);
   
   // This doesn't work
   fn1((const char **)&buffer,1); 
}
~~~
EEVBlog Members - get yourself 10% discount off all my electronic components for sale just use the Buy Direct links and use Coupon Code "eevblog" during checkout.  Shipping from New Zealand, international orders welcome :-)
 

Online IanB

  • Super Contributor
  • ***
  • Posts: 11891
  • Country: us
Re: C/C++ question - casting a char * into a char **
« Reply #1 on: November 29, 2014, 05:00:56 am »
This works:

Code: [Select]
#include <stdio.h>
#include <string.h>

void fn1(const char **arrayOfStrings, int arrayLength)
{
  printf(arrayOfStrings[0]);
}

void fn2(const char *singleString)
{
  fn1(&singleString, 1);
}

int main()
{
   char string[] = {"HELLOWORLD\n"};
   char *buffer = string;
   
   // This works
   printf("Calling fn2:\n");
   fn2(buffer);
   
   // This also works
   printf("Calling fn1:\n");
   fn1(&buffer,1); 
}

Can you see why?
« Last Edit: November 29, 2014, 05:03:17 am by IanB »
 

Online IanB

  • Super Contributor
  • ***
  • Posts: 11891
  • Country: us
Re: C/C++ question - casting a char * into a char **
« Reply #2 on: November 29, 2014, 05:02:05 am »
By the way, it's not C++, it's C. When you write <stdio.h> rather than <cstdio>, you are writing C.
 

Offline sleemanjTopic starter

  • Super Contributor
  • ***
  • Posts: 3024
  • Country: nz
  • Professional tightwad.
    • The electronics hobby components I sell.
Re: C/C++ question - casting a char * into a char **
« Reply #3 on: November 29, 2014, 05:26:42 am »
This works:

But it does not answer my question.

~~~
EEVBlog Members - get yourself 10% discount off all my electronic components for sale just use the Buy Direct links and use Coupon Code "eevblog" during checkout.  Shipping from New Zealand, international orders welcome :-)
 

Offline sleemanjTopic starter

  • Super Contributor
  • ***
  • Posts: 3024
  • Country: nz
  • Professional tightwad.
    • The electronics hobby components I sell.
Re: C/C++ question - casting a char * into a char **
« Reply #4 on: November 29, 2014, 05:27:13 am »
By the way, it's not C++, it's C. When you write <stdio.h> rather than <cstdio>, you are writing C.

C++ is a superset of C, by definition, everything C is C++
~~~
EEVBlog Members - get yourself 10% discount off all my electronic components for sale just use the Buy Direct links and use Coupon Code "eevblog" during checkout.  Shipping from New Zealand, international orders welcome :-)
 

Online IanB

  • Super Contributor
  • ***
  • Posts: 11891
  • Country: us
Re: C/C++ question - casting a char * into a char **
« Reply #5 on: November 29, 2014, 05:32:17 am »
C++ is a superset of C, by definition, everything C is C++

A common misconception, but no, not exactly. The intersection of C and C++ is a subset of both, but neither is fully contained in the other.

If you are writing code it is a good idea to decide in advance whether you are writing C or C++ and then stick to the one or the other.
« Last Edit: November 29, 2014, 05:34:31 am by IanB »
 

Online IanB

  • Super Contributor
  • ***
  • Posts: 11891
  • Country: us
Re: C/C++ question - casting a char * into a char **
« Reply #6 on: November 29, 2014, 05:33:17 am »
But it does not answer my question.

That was the exercise for the student. I changed the code in a subtle, but significant way.

Hint: it's about data types.
 

Offline sleemanjTopic starter

  • Super Contributor
  • ***
  • Posts: 3024
  • Country: nz
  • Professional tightwad.
    • The electronics hobby components I sell.
Re: C/C++ question - casting a char * into a char **
« Reply #7 on: November 29, 2014, 05:34:49 am »
A common misconception, but no, not exactly.

Mmm, offtopic, but what's a piece of C code that is NOT valid C++ then.  And by valid, I mean, will not compile in modern compilers and produce the result you desire -  not just "technically invalid according to Stroustrup"

~~~
EEVBlog Members - get yourself 10% discount off all my electronic components for sale just use the Buy Direct links and use Coupon Code "eevblog" during checkout.  Shipping from New Zealand, international orders welcome :-)
 

Online IanB

  • Super Contributor
  • ***
  • Posts: 11891
  • Country: us
Re: C/C++ question - casting a char * into a char **
« Reply #8 on: November 29, 2014, 05:37:23 am »
Mmm, offtopic, but what's a piece of C code that is NOT valid C++ then.  And by valid, I mean, will not compile in modern compilers and produce the result you desire -  not just "technically invalid according to Stroustrup"

You can Google it, but here are a few examples:

http://www.cprogramming.com/tutorial/c-vs-c++.html
 

Offline sleemanjTopic starter

  • Super Contributor
  • ***
  • Posts: 3024
  • Country: nz
  • Professional tightwad.
    • The electronics hobby components I sell.
Re: C/C++ question - casting a char * into a char **
« Reply #9 on: November 29, 2014, 05:39:59 am »
But it does not answer my question.

That was the exercise for the student. I changed the code in a subtle, but significant way.

Hint: it's about data types.

Yes, I realise it is about datatypes, that's why I'm casting it.

In my example, the one that works goes through effectively two casts,
  char * => const char *  (cast into the call to fn2 from main)
  const char * => char ** (cast into the call to fn1 from fn2)

The one that doesn't is a single type cast, which to my eye, is identical to the combination of the two typecasts.
  char * => const char ** (cast into the calll to fn1 from main)

My question is, why is the typecast in the second example not equivalent to the effective two typecasts in the first example.

~~~
EEVBlog Members - get yourself 10% discount off all my electronic components for sale just use the Buy Direct links and use Coupon Code "eevblog" during checkout.  Shipping from New Zealand, international orders welcome :-)
 

Online IanB

  • Super Contributor
  • ***
  • Posts: 11891
  • Country: us
Re: C/C++ question - casting a char * into a char **
« Reply #10 on: November 29, 2014, 05:42:32 am »
But it does not answer my question.

In your original program, the data type of "buffer" is "array[12] of char". This data type is not the same as "pointer to char". If you take the address of "buffer", you get "pointer to array[12] of char". This is not the same as "pointer to pointer to char". You can't cast one to the other because pointers in C know the size of the thing they are pointing at (well, at least the compiler knows).

In my modified program, I made "buffer" be "pointer to char". Taking the address of "buffer" now yields "pointer to pointer to char" and the cast becomes permissible.
 

Offline sleemanjTopic starter

  • Super Contributor
  • ***
  • Posts: 3024
  • Country: nz
  • Professional tightwad.
    • The electronics hobby components I sell.
Re: C/C++ question - casting a char * into a char **
« Reply #11 on: November 29, 2014, 05:53:35 am »
In my modified program, I made "buffer" be "pointer to char". Taking the address of "buffer" now yields "pointer to pointer to char" and the cast becomes permissible.

Ok, yeah, I see I think, I always tend to think of C arrays as being equivalent to an unchangable malloc'd pointer of the appropriate size, just arrays are on stack and malloc is on heap, but, yeah, it's not really I suppose.

Is it not possible to cast the character array to a usable (const char **)  [of length 1] in a single step?

My first example achieves it (empirically), but through the indirect step another function call.
« Last Edit: November 29, 2014, 05:56:08 am by sleemanj »
~~~
EEVBlog Members - get yourself 10% discount off all my electronic components for sale just use the Buy Direct links and use Coupon Code "eevblog" during checkout.  Shipping from New Zealand, international orders welcome :-)
 

Online IanB

  • Super Contributor
  • ***
  • Posts: 11891
  • Country: us
Re: C/C++ question - casting a char * into a char **
« Reply #12 on: November 29, 2014, 05:57:42 am »
Is it not possible to cast the character array to a usable (const char **)  [of length 1] in a single step?

I can't figure out how. To be honest, the intricacies of C make my brain hurt and I find it easiest to follow the path of least resistance...
 

Online IanB

  • Super Contributor
  • ***
  • Posts: 11891
  • Country: us
Re: C/C++ question - casting a char * into a char **
« Reply #13 on: November 29, 2014, 06:00:40 am »
By the way, it is very unusual that you should need to use casts in C (or C++). Using casts is a danger sign that you are entering dragon territory.
 

Offline Royce

  • Contributor
  • Posts: 49
  • Country: us
Re: C/C++ question - casting a char * into a char **
« Reply #14 on: November 29, 2014, 06:17:05 am »
My best stab at an explanation is that there is no pointer variable that points to the beginning of the string when you define it that way.

When the compiler is laying down memory, lets say it decides that buffer starts at memory location 34. Whenever, you say buffer in your code, it doesn't make an indirect reference to some OTHER piece of memory that contains the number 34, it just puts 34 into the op codes at the point that you refer to buffer.

That non-existent memory address containing variable is what you are trying to reference in your code.  That's why you have to explicitly create the char** so the compiler lays down a chunk of memory capable of containing the memory address of the beginning of a string.

Just plain buffer, in the sense that it has value 34, is kind of a pointer literal and taking its address is analogous to int* b = &5. You can't really get the address of the number 5.

I could be off base, but that is my sense of what is going on.
 

Offline adam1213

  • Regular Contributor
  • *
  • Posts: 120
  • Country: au
Re: C/C++ question - casting a char * into a char **
« Reply #15 on: November 29, 2014, 06:34:18 am »
Reason for not being able to cast:

void fn1(const char **arrayOfStrings, int arrayLength)

This function takes in an array of pointers.
e.g.
   char string1[] = "string1";
   const char* arrayOfStrings[] = {string1};
   fn1(arrayOfStrings, 1);

You could also write
   const char* string1 = "string1";
   fn1(&string1, 1);
This is somewhat more cryptic but works the same way. fn1 uses &string1 as an array with one element.

What does not work is writing:
   char string1[] = "string1";
   const char **temp = &string1;

The reason this does not work is that this tries to set temp, a pointer to a pointer to a pointer.

Is it not possible to cast the character array to a usable (const char **)  [of length 1] in a single step?
If you really wanted to write it e.g. for the purpose of understanding c it is possible. Have a look at the following. Just don't use it in a real program.
Code: [Select]
int main(void)
{
char string1[] = "string1";
char string2[20];

char** string2AsArrayOfPointers = (char**) &string2;  // Example for demonstration purposes only. DO NOT USE THIS CODE IN A REAL PROGRAM.
string2AsArrayOfPointers[0] = string1;

printf("result: %s\n", string2AsArrayOfPointers[0]);

return 0;
}
The way this code works is that, string2 which is an array is cast into an array of pointers. The first element is set to point to the location of string1. This enables "string2" to be used as if it was really an array of pointers (note it would be much simpler to simply use use an array of pointers)


Quote from: sleemanj
"Ok, yeah, I see I think, I always tend to think of C arrays as being equivalent to an unchangable malloc'd pointer of the appropriate size, just arrays are on stack and malloc is on heap, but, yeah, it's not really I suppose."
they are..
Your original thinking is correct. What is going on here relates to how ** works. ** means you are passing a pointer to an array of pointers (or a single pointer). If you try to pass a pointer then the value pointed to by the pointer will be used to lookup the value rather than being used as a value.
« Last Edit: November 29, 2014, 06:43:40 am by adam1213 »
 

Offline adam1213

  • Regular Contributor
  • *
  • Posts: 120
  • Country: au
Re: C/C++ question - casting a char * into a char **
« Reply #16 on: November 29, 2014, 06:39:27 am »
In terms of the original program: if you wanted to include the code in a a real program rather than discuss casting, a simpler way of writing it would be to have a function which prints a single string. The function which prints using an array of string can call the one for a single string.

Other things to note:

char * => const char *  - this can be done with an implicit cast e.g.
   char buffer[1] = "";
   char* constString = buffer;

printf(arrayOfStrings[0]); // warning: format not a string literal and no format arguments [-Wformat-security]
It is better to write "printf("%s", arrayOfStrings[0]); or puts(arrayOfStrings[0]). The reason for this is that if the string contains entries such as "%d", printf will try print an integer in place of just printing "%d". Worse if the string can be controlled by a user, your program may be vulnerable to arbitrary code execution.
 

Online IanB

  • Super Contributor
  • ***
  • Posts: 11891
  • Country: us
Re: C/C++ question - casting a char * into a char **
« Reply #17 on: November 29, 2014, 07:25:22 am »
Another way of looking at the problem of the first post is by comparison with a simpler example. Consider the following two declarations:
Code: [Select]
    char buffer[] = "something";
    int value = 10;
Here both "buffer" and "value" are variables. If we refer to "value" we get something of type "int". If we refer to "&value" we get the memory address of the variable, which is a constant. So if we tried to refer to "&&value" we would be trying to take the address of a constant, which is not permitted.

Now consider a similar scenario with "buffer". The variable "buffer" is an array of characters. According to how the C language works, if we refer to "buffer" we get the memory address where the array begins, which is a constant (note the similarity with "&value"). Consequently, if we try to refer to "&buffer" we are trying to take the address of a constant, which similarly to "&&value" is not permitted. We can trick the compiler into doing it using casts, but the resulting program will not work.

On the other hand, if we write:
Code: [Select]
    char *string = "something";
then "string" is now a variable (not a constant), and the value of the variable can be assigned to. If we refer to "string" we get a value of type "char *" (pointer to char). If we refer to "&string" we get the memory address of "string", which is allowed (just as "&value" was allowed).
 

Offline coderAndHackerNW

  • Newbie
  • Posts: 5
Re: C/C++ question - casting a char * into a char **
« Reply #18 on: November 29, 2014, 07:55:33 am »
You can  compare the assembler output of the two to see what the difference is from the computer's point of view.
gcc -save-temps test.c
worked for me to get the assembler listing.
Oh, and for those who didn't know, some compilers emit assembler code that can be hand tuned, and used for debugging.
-Bob
 

Offline westfw

  • Super Contributor
  • ***
  • Posts: 4199
  • Country: us
Re: C/C++ question - casting a char * into a char **
« Reply #19 on: November 29, 2014, 10:25:35 am »
Quote
but what's a piece of C code that is NOT valid C++ then.
It seems that C and C++ have been diverging.
Newer C standards apparently support "named address spaces" useful for Harvard architecture microcontrollers (avr-gcc 4.8.x uses this to implement "__flash", for example.)
This is apparently NOT supported by C++ ...
http://www.avrfreaks.net/forum/avr-g-lacks-flash
 

Offline adam1213

  • Regular Contributor
  • *
  • Posts: 120
  • Country: au
Re: C/C++ question - casting a char * into a char **
« Reply #20 on: November 29, 2014, 11:34:25 am »
Mmm, offtopic, but what's a piece of C code that is NOT valid C++ then.  And by valid, I mean, will not compile in modern compilers and produce the result you desire -  not just "technically invalid according to Stroustrup"

Code: [Select]
void main()
{
}
c++: error: ‘::main’ must return ‘int’ void main()

Code: [Select]
int main()
{
functionLocatedAfterMain();
}

void functionLocatedAfterMain()
{
}
In function ‘int main()’:
c++: error: ‘functionLocatedAfterMain’ was not declared in this scope
c: warning, depending on compiler flags

Code: [Select]
void function()
{
}

int main()
{
function(1);
}
c++: error: too many arguments to function ‘void function()’
c: this compiles as by default the function has an integer parameter. In c++ the function has no parameters
« Last Edit: November 29, 2014, 11:43:16 am by adam1213 »
 

Offline Jeroen3

  • Super Contributor
  • ***
  • Posts: 4078
  • Country: nl
  • Embedded Engineer
    • jeroen3.nl
Re: C/C++ question - casting a char * into a char **
« Reply #21 on: November 29, 2014, 02:40:02 pm »
Another way of looking at the problem of the first post is by comparison with a simpler example. Consider the following two declarations:
Code: [Select]
    char buffer[] = "something";
    int value = 10;
When you using a string literals such as "something". It will be replaced by the address of the first character.
Like this: &'s' of the string "something". And a pointer is always one word wide.
 

Online IanB

  • Super Contributor
  • ***
  • Posts: 11891
  • Country: us
Re: C/C++ question - casting a char * into a char **
« Reply #22 on: November 29, 2014, 02:50:36 pm »
Another way of looking at the problem of the first post is by comparison with a simpler example. Consider the following two declarations:
Code: [Select]
    char buffer[] = "something";
    int value = 10;
When you using a string literals such as "something". It will be replaced by the address of the first character.
Like this: &'s' of the string "something". And a pointer is always one word wide.

That's not the case here. The string literal on the right of the '=' is the initializer for the array on the left. It's exactly the same as writing this:
Code: [Select]
    char buffer[] = {'s', 'o', 'm', 'e', 't', 'h', 'i', 'n', 'g', 0};
You can see from this that there is no address of the 's' character, it is just data to initialize the array. (The compiler will count the number of elements in the initializer and allocate space for an array of size 10 in this case.)

If instead I wrote this:
Code: [Select]
    char *not_a_buffer = "something";
Then the situation would be as you describe. In this case I will have initialized a pointer to point to the first character of the string literal allocated to a constant data area by the compiler.
 

Offline andersm

  • Super Contributor
  • ***
  • Posts: 1198
  • Country: fi
Re: C/C++ question - casting a char * into a char **
« Reply #23 on: November 29, 2014, 04:18:56 pm »
Newer C standards apparently support "named address spaces" useful for Harvard architecture microcontrollers (avr-gcc 4.8.x uses this to implement "__flash", for example.)
I believe the extensions for embedded C are still only at the draft technical report level.

Offline Jeroen3

  • Super Contributor
  • ***
  • Posts: 4078
  • Country: nl
  • Embedded Engineer
    • jeroen3.nl
Re: C/C++ question - casting a char * into a char **
« Reply #24 on: November 29, 2014, 05:23:07 pm »
That's not the case here. The string literal on the right of the '=' is the initializer for the array on the left. It's exactly the same as writing this:
char buffer[] is equal to char *buffer, the only thing different is that you can run sizeof(buffer) on first one.
If you want to know the length of the second one you'd need strlen().
Remember the first lesson of pointers. An array is a pointer to its first element. (as is a struct)

When used on an array you initialize it with a pointer to 's' of "something". And the compiler stores the length.
« Last Edit: November 29, 2014, 05:24:44 pm by Jeroen3 »
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf