tags:

views:

274

answers:

7

Confused with the problem here. New to C, as made obvious by the below example:

#include <stdlib.h>
#include <stdio.h>

void pass_char_ref(unsigned char*);

int main()
{
  unsigned char bar[6];

  pass_char_ref(&bar);

  printf("str: %s", bar);

  return 0;
}

void pass_char_ref(unsigned char *foo)
{
  foo = "hello";
}

To my understanding, bar is an unsigned character array with an element size of 6 set away in static storage. I simply want to pass bar by reference to pass_char_ref() and set the character array in that function, then print it back in main().

A: 

Since bar[] is an array, when you write bar, then you are using a pointer to the first element of this array. So, instead of:

pass_char_ref(&bar);

you should write:

pass_char_ref(bar);
Alex
+7  A: 

You need to copy the string into the array:

void pass_char_ref(unsigned char *foo) 
{ 
  strcpy( foo, "hello" ); 
} 

Then when you call the function, simply use the array's name:

pass_char_ref( bar );

Also, the array is not in "static storage"; it is an automatic object, created on the stack, with a lifetime of the containing function's call.

anon
True! I haven't noticed that, I only focused in the pointer argument! I'm not editing my answer though, since Neil mentioned that first!
Alex
`foo` should be cast to a `char *`.
Jason
Array variables are *not* pointers; I'm sure you know this, but why word it in a way that will confuse the OP and others? The real answer is using strcpy.
Roger Pate
True - arrays are not pointers. However, the name of an array is a pointer to the array's first element.
anon
No, it is not. It only degrades into a pointer to the first element easily, such as when assigned to a pointer or passed as a parameter.
Roger Pate
So when I say a[1], you don't think that this is equivalent to *(a +1)?
anon
It is exactly equivalent, because C defines [] exactly that way (`1[a]` is also identical for this reason). In nearly the same breath, the C standard says this works for arrays because they degrade into pointers. The easiest way to see that array variables ("a" or "bar") are not pointers is to use sizeof on them.
Roger Pate
@Roger: Arrays and pointers are not the same. The zero'th element of the array always decay into a pointer or the name of the array is a pointer to the zero'th element of the array. Yes it's true that a[1] is same as *(a + 1) or *(1 + a) or 1[a] as pointers and array expressions are commutative where subscripting is concerned, but in the context of pointers, they are inherently the same but the subtlety lies in how it is accessed in memory!
tommieb75
Technically "hello" actually does go in static storage, but saying this just complicates things further.
tgamblin
Sigh. http://c-faq.com/aryptr/aryptrequiv.html
jamesdlin
A: 

The use of the address of operator (&) on arrays is no longer allowed. I agree that it makes more sense to do &bar rather than bar, but since arrays are ALWAYS passed by reference, the use of & is redundant, and with the advent of C++ the standards committee made it illegal.

so just resist the urge to put & before bar and you will be fine.

Edit: after a conversation with Roger, I retract the word illegal. It's legal, just not useful.

John Knoeller
Roger Pate
really? in what context would you be allowed to do this?
John Knoeller
Roger Pate
Yes and int (*)[3] can't be used for anything. Try it.
John Knoeller
You can assign it to a variable, pass it to a function, dereference it, subscript it, print it, etc. You can do anything with it that you can do with any other pointer. That *you* don't have a use for it is *vastly* different from the standard making it illegal.
Roger Pate
@Roger: pass it to a function, show the protype. go ahead, I'll wait.
John Knoeller
`void f(int (*p)[3]); int a[3]; f(`
Roger Pate
Another (and exactly equivalent): `void f(int p[][3]);`
Roger Pate
Fair enough, p[][3] is just a bug if you use it to refer to a[3]. but the other is wierd but legal.
John Knoeller
It's not a bug; both are identical: parameter types of the form T[] mean the same as T*. Here T is int[3], so int[][3] is the same as int (*)[3]. You could say not passing the size of the array is error-prone, but if you're assuming it's 1 for the first then the same assumption could apply to the second.
Roger Pate
Furthermore, given an array of type `T a[M][N]`, the type of `a` will be implicitly converted from `T [M][N]` to `T (*)[N]`. Thus, if you pass a 2D array to a function like `int a[4][5]; f(a);`, then the function prototype must be `void f(int (*a)[5]) {...}`.
John Bode
+4  A: 

Two things:

  1. You don't need to pass &bar; just pass bar.

    When you pass an array like this, the address of its first (0th) element is passed to the function as a pointer. So, call pass_char_ref like this:

    pass_char_ref(bar);
    

    When you call pass_char_ref like this, the array name "decays" into a pointer to the array's first element. There's more on this in this tutorial, but the short story is that you can use an array's name in expressions as a synonym for &array_name[0].

  2. Pointers are passed by value. You have:

    void pass_char_ref(unsigned char *foo)
    {
      foo = "hello";
    }
    

    In some other languages, arguments are passed by reference, so formal parameters are essentially aliases for the arguments. In such a language, you could assign "hello" to foo and it would change the contents of bar.

    Since this is C, foo is a copy of the pointer that's passed in. So, foo = "hello"; doesn't actually affect bar; it sets the local value (foo) to point to the const string "hello".

    To get something like pass by reference in C, you have to pass pointers by value, then modify what they point to. e.g.:

    #include <string.h>
    void pass_char_ref(unsigned char *foo)
    {
      strcpy(foo, "hello");
    }
    

    This will copy the string "hello" to the memory location pointed to by foo. Since you passed in the address of bar, the strcpy will write to bar.

    For more info on strcpy, you can look at its man page.

tgamblin
+2  A: 

In C, arrays are accessed using similar mechanics to pointers, but they're very different in how the definitions work - an array definition actually causes the space for the array to be allocated. A pointer definition will cause enough storage to be allocated to refer (or "point") to some other part of memory.

unsigned char bar[6];

creates storage for 6 unsigned characters. The C array semantics say that, when you pass an array to another function, instead of creating a copy of the array on the stack, a pointer to the first element in the array is given as the parameter to the function instead. This means that

void pass_char_ref(unsigned char *foo)

is not taking an array as an argument, but a pointer to the array. Updating the pointer value (as in foo = "hello";, which overwrites the pointer's value with the address of the compiled-in string "hello") does not affect the original array. You modify the original array by dereferencing the pointer, and overwriting the memory location it points to. This is something that the strcpy routine does internally, and this is why people are suggesting you use

void pass_char_ref(unsigned char *foo)
{
    strcpy(foo, "hello");
}

instead. You could also say (for sake of exposition):

void pass_char_ref(unsigned char *foo)
{
    foo[0] = 'h';
    foo[1] = 'e';
    foo[2] = 'l';
    foo[3] = 'l';
    foo[4] = 'o';
    foo[5] = 0;
}

and it would behave correctly, too. (this is similar to how strcpy will behave internally.)

HTH

Aidan Cully
A: 

Please see here to an explanation of pointers and pass by reference to a question by another SO poster. Also, here is another thorough explanation of the differences between character pointers and character arrays.

Your code is incorrect as in ANSI C standard, you cannot pass an array to a function and pass it by reference - other data-types other than char are capable of doing that. Furthermore, the code is incorrect,

void pass_char_ref(unsigned char *foo)
{
  foo = "hello";
}

You cannot assign a pointer in this fashion to a string literal as pointers use the lvalue and rvalue assignment semantics (left value and right value respectively). A string literal is not an rvalue hence it will fail. Incidentally, in the second link that I have given which explains the differences between pointers and arrays, I mentioned an excellent book which will explain a lot about pointers on that second link.

This code will probably make more sense in what you are trying to achieve

void pass_char_ref(unsigned char *foo)
{
  strcpy(foo, "hello");
}

In your main() it would be like this

int main()
{
  unsigned char bar[6];

  pass_char_ref(bar);

  printf("str: %s", bar);

  return 0;
}

Don't forget to add another line to the top of your code #include <string.h>.

Hope this helps, Best regards, Tom.

tommieb75
Actually `foo = "hello"` will *not* fail (it just won't do what the OP wants), and string literals *are* rvalues. Other data types are no different than char, as used here, as you suggest, either.
Roger Pate
@Roger: To be pedantic, I thought the only time you could use a string literal in an assignment expression is when you define a pointer to char or to an array of chars..that's at compile time - the definition and declaration, not at run-time...
tommieb75
The initialization happens at runtime (it has to: `char s[] = "abc";`, if s is not global, it's an auto variable in its containing function). Just because arrays are not assignable doesn't mean they aren't rvalues (a non-string-literal array can even be an lvalue). C99 has abandoned the 'rvalue' term and even re-defined 'lvalue'. (Quote from page 46, in my copy of C99, in the next comment.)
Roger Pate
The name "lvalue" comes originally from the assignment expression `E1 = E2`, in which the left operand `E1` is required to be a (modifiable) lvalue. It is perhaps better considered as representing an object "locator value". What is sometimes called "rvalue" is in this International Standard described as the "value of an expression". [C99, footnote 53, also see 6.3.2.1]
Roger Pate
A: 

Time again for the usual spiel --

When an expression of array type appears in most contexts, its type is implicitly converted from "N-element array of T" to "pointer to T" and its value is set to point to the first element of the array. The exceptions to this rule are when the array expression is the operand of either the sizeof or & operators, or when the array is a string litereal being used as an initializer in a declaration.

So what does all that mean in the context of your code?

The type of the expression bar is "6-element array of unsigned char" (unsigned char [6]); in most cases, the type would be implicitly converted to "pointer to unsigned char" (unsigned char *). However, when you call pass_char_ref, you call it as

pass_char_ref(&bar);

The & operator prevents the implicit conversion from taking place, and the type of the expression &bar is "pointer to 6-element array of unsigned char" (unsigned char (*)[6]), which obviously doesn't match the prototype

void pass_char_ref(unsigned char *foo) {...}

In this particular case, the right answer is to ditch the & in the function call and call it as

pass_char_ref(bar);

Now for the second issue. In C, you cannot assign string values using the = operator the way you can in C++ and other languages. In C, a string is an array of char with a terminating 0, and you cannot use = to assign the contents of one array to another. You must use a library function like strcpy, which expects parameters of type char *:

void pass_char_ref(unsigned char *foo)
{ 
  strcpy((char *)foo, "hello");
}

Here's a table of array expressions, their corresponding types, and any implicit conversions, assuming a 1-d array of type T (T a[N]):

Expression             Type                Implicitly converted to
----------             ----                -----------------------
         a             T [N]               T *
        &a             T (*)[N]          
      a[0]             T 
     &a[0]             T *

Note that the expressions a, &a, and &a[0] all give the same value (the address of the first element in the array), but the types are all different.

John Bode