views:

174

answers:

5

I'm having troubles when calling a function taking a pointer to a string as a parameter. I need to get an Element's name.

// method
void getStringFromCsv( char ** str );

Let me introduce the structures I'm working with (not written by me and part of a much bigger project, I can't modify them).

// typedefs
typedef char      T_CHAR64[64];
typedef T_CHAR64  T_SYMBOL;

// generic element
typedef struct Element
{
  T_SYMBOL  name; 
} T_Element;

// csv element
typedef struct CsvElement
{
  Element * pElement;
  int   id;
} T_csvElement;

So, basically, I thought I would call the function like this :

T_Element * pData; // Not null, filled earlier
getStringFromCsv( &pData->pElement->name );

But this doesn't work (warning: passing argument 1 of ‘STR_toCsv’ from incompatible pointer type). I'm using gcc with NetBeans 6.8.

I tried many things...

T_SYMBOL foo = "foo";
T_SYMBOL * pFoo = &foo;

getStringFromCsv( pDef->name, &pFoo ); // error : passing from incompatible pointer type

T_CHAR * pBar = &foo;      // error : init from incompatible pointer type
T_CHAR * pBaz = &(foo[0]); // OK

getStringFromCsv( pDef->name, &pBaz ); // OK

T_SYMBOL * pFooTest = &(foo[0]); // error : init from incompatible pointer type

...but ended up casting name to a char ** :

getStringFromCsv( (char**) &pData->pElement->name );

What is wrong with my code ? Basically, SYMBOL = CHAR *, right ? Why is SYMBOL* != CHAR** ? I'm pretty sure I'm missing something simple but right now... Nothing came.

EDIT Here is getStringFromCsv :

void getStringFromCsv( char ** data )
{
  // pDesc is defined and not null
  csvDescriptorCat( pDesc, *data);
  csvDescriptorCat( pDesc, "\t");
}

void csvDescriptorCat( CsvDescriptor * pDesc, char* str)
{
  int len;
  if( str != NULL)
  {
    len = strlen(str);
    strcpy( &pDesc->line[pDesc->pos], str);
    pDesc->pos += len;
  }
}
A: 

Yes, under the covers T_SYMBOL is handled like a char *. But you've declared it as a char[64], so you're passing in a pointer to a char[64] not a pointer to a pointer to a char. The compiler is keeping track of that for you.

Personally in this situation I would just cast it as you did at the end of your question.

Vicky
No. Casting it will not help, because it will simply crash when you try to dereference the nonsense `char *` value you get by dereferencing the casted value.
caf
Thanks for the explanation anyways.
Isaac Clarke
+2  A: 

name is an array of chars, so &name gives you a pointer to char[64], as Vicky already answered. But casting makes things worse, because it tells the compiler to treat the first chars of the array as a pointer to the real array.

See the C-FAQ: http://c-faq.com/aryptr/aryptr2.html

I think you can use a temporary char* here:

char *tmp = pData->pElement->name; // array decays to pointer
getStringFromCsv(&tmp);

If this is expected by the function. Expecting a char**, make sure that it doesn't try to reallocate the memory. For simply filling it, a char* would be enough.

Secure
Thanks ! I don't like the idea of a temporary variable, though... Is the cast worse ?
Isaac Clarke
The cast actually results in undefined behaviour and will most likely result in a crash. Read the documentation of `getStringFromCsv` carefully, as I said, as there usually is a reason why one level of indirection more than necessary is used. If you give us this documentation, then we can suggest specific workarounds, but without knowing it we can only guess.
Secure
I don't like having to use a temporary variable either. However, this solution is the only way that I can think of to safely do this. The ideal way would be to change the function prototype, but you mentioned that wasn't an option. As Secure mentioned, the function better not be trying to modify `str` itself or you can run into big problems.
bta
What's wrong with a temporary variable? Function-local variables on the stack are cheap, and if I had to use `pData->pElement->name` more than once, then I would store it in a variable anyway for convenience and readability. Not calling it `tmp`, of course. Granted, here it is a sign that two designs are not matching properly and require some glue code.
Secure
I edited the first post to show you getStringFromCsv. // My main concern about creating a variable is reusability. My code will be used outside, and not only by the main developpers. I would like the users to be able to code, knowing only the headers. A cast would be "logical". Thanks for you help !
Isaac Clarke
Whoa, this is a major WTF. First, a `char **` is the wrong type to use here, a `const char *` would be the right choice, because it is only copied to pDesc. Second and more important, if this is the full `csvDescriptorCat`, then there is no length control when doing so. Especially when this code will be used outside, then this is a buffer overflow waiting to happen.
Secure
In C, I've found that a cast is never "logical". The only circumstances where casts are useful and sometimes needed is when working with different integer types on the bit level or for specific arithmetic, and when working with variadic functions. Most other casts should be treated as a **warning sign** that something may be wrong. Don't forget that a cast tells the compiler "I know what I'm doing, so shut up and just do it." Especially here, it is IMHO not the case. Instead it is used to hide a fundamental misunderstanding of the type system regarding arrays and pointers, thereby causing UB.
Secure
+2  A: 

If you wish to pass &pData->pElement->name to the function, the function must be declared as:

void getStringFromCsv(T_SYMBOL * str);

Alternatively you can use a temporary char * as Secure offered - but there's not much point in doing this, because any updates to that char *'s value can't be used - the ->name member can't be modified, as it's an array.

You might as well just declare the function as:

void getStringFromCsv( char * str );

...and call it as:

getStringFromCsv( pData->pElement->name );

(In this case, the function can still change the contents of the ->name array. What you can't do is to change the position of the array itself).


As well as Secure's option, there's another way if your compiler supports C99 compound literals:

getStringFromCsv( &(char *){ pData->pElement->name } );
caf
Modifiying function prototypes is *not* an option, unfortunately. Thanks
Isaac Clarke
@Isaac Clarke: In that case, you'll need to edit your question to show the source of the `getStringFromCsv()` function - or at least its documented interface.
caf
Done ;) (sorry for the delay, we're not on the same time zone I presume )
Isaac Clarke
@Isaac Clarke: Since the function is only reading `*data`, and not altering it, the temporary `char *` solution is the correct one to use (but as you can see, there's no earthly reason why the function couldn't just take a `char *` in the first place, using `data` in place of `*data`!)
caf
The main developper himself told me it was an error of his. Changing this would imply a massive refactoring, so it's not really an option either. I'm gonna go with the temp char, thanks.
Isaac Clarke
Hey ! The C99 method works fine ! That's what I was looking for, thanks !
Isaac Clarke
@Isaac Clarke: No problem. By the way, you should look into Coccinelle - http://coccinelle.lip6.fr/ - the `spatch` semantic patch utility would make short work of such a refactoring.
caf
Oh, a french tool :) I know a few guys from INRIA. I'll keep that in mind, thanks ! // I think the refactoring is not a problem in itself, I'm more concerned about my coworker's will to change... But that's another issue.
Isaac Clarke
A: 

Considering this : http://c-faq.com/decl/strlitinit.html

char  a[4] = "hello";
char* p = "hello";

Aren't the same thing (even if they seem to be). So my SYMBOL and CHAR* cannot be exchanged, right ?

Is there a workaround, or another solution ?

Isaac Clarke
+1  A: 

Alas, one of the little secrets of C that people fail to tell you, an array is not the same thing as a pointer. if x is defined as int x[5] or whatever, &x == x. Try out this code below:

#include <stdio.h>
int main(int argc, const char *argv[])
{
   char x [5];
   char *y;

   printf("%08x\n", x);
   printf("%08x\n", &x);
   printf("%08x\n", y);
   printf("%08x\n", &y);

   return 0;
}
Rannick