2

In one of my course's tutorial videos, I have encountered a situation where pass by value and pass by reference is occurring. In the example he gives us, he, firstly, admits that the code is written poorly, and then asks us to find out what exactly would be printed. The relevant code is as follows:

void set_array(int array[4]);
void set_int(int x);

int main(void){
    int a = 10; 
    int b[4] = {0, 1, 2, 3);
    set_int(a);
    set_array(b);
    printf("%d %d\n", a, b[0]);
}

void set_array(int array[4]){
    array[0] = 22;
}

void set_int(int x){
    x = 22;
}

What I want to make sure, after initially coming up with the wrong output, is that I'm understanding why the correct output of "10, 22" is being printed.

The value of 10 is being printed because when the set_int function is being called, its parameter, being a variable, means that any variable can be passed. It is not exclusive to only the value 22 as defined in the set_int function.

The value of 22 is being printed because, when the set_array function is being called, its parameter, being an array, means that only the original value can be passed because it points to a specific memory location where the value 22 is stored.

Have I misunderstood what is happening, and am I missing any crucial points?

  • 1
    The parameter in set_int is being passed by value, also called "pass by copy". The variable 'a' is not modified by the function. The parameter in set_array is passed by reference because the name of an array evaluates to a pointer to the first element of the array. This means that b[0] is modified by the function. – Jim Rogers Jan 13 '20 at 14:27

2 Answers2

10

By Definition, there is no pass-by-reference in function calls in C. Both your examples use pass by value. However, depending on the usage, the result varies (i.e., we can emulate the behavior of passing by reference).

  • In the first case, the variable a is passed by value, hence it cannot be changed from the function call.

  • In the second case, the variable b is passed by value, hence it is also cannot be changed from the function call.

    However, in the second case, there is a special phenomena that takes place. The argument which is passed, b is an array. An array, while passed as function argument (among many other usage), decays to a pointer to the first element of the array. So essentially, you're passing a pointer, and that value at the memory address pointed to by the pointer can be modified (but not the pointer itself). That's why, the change from the set_array() function persists in the main().

    In another word, the second case is equivalent to

    void set_pointer(int *ptr);
    void set_int(int x);
    
    int main(void){
        int a = 10; 
        int b[4] = {0, 1, 2, 3);
        int * p = &(b[0]);         // address of the first element
        set_int(a);
        set_pointer(p);           // pass the pointer
        printf("%d %d\n", a, b[0]);
    }
    
    void set_array(int *ptr){
        *ptr = 22;                 // operate on pointer
    }
    
    void set_int(int x){
        x = 22;
    }
    
Sourav Ghosh
  • 133,132
  • 16
  • 183
  • 261
  • 2
    It would be worth calling out explicitly that using array syntax to declare a function parameter in fact declares the parameter's type as the corresponding pointer type, not actually as an array. This important subtlety can be confusing, so I consider it poor form to declare parameters to have complete array types. – John Bollinger Jan 13 '20 at 14:36
  • 2
    `set_array(b)` passes `b` by reference. The array `b` is not passed. A reference to it is passed. Do not confuse the fact that C++ used “reference” as a name for a certain language feature with the fact that “reference” is an English word used to describe this. A pointer refers to the object(s) it points to. `set_array(b)` and `void set_array(int array[4]) …` have the features and behavior of passing by reference. The fact that the mechanism is specified in the standard to be a conversion and an adjustment to a pointer does not negate the fact that it is an implementation of pass-by-reference. – Eric Postpischil Jan 13 '20 at 14:40
  • 1
    @EricPostpischil "Passing b by reference" would mean that you could do `b = something else;` inside `set_array` and have it affect `b` in `main`, which is not the case. – user253751 Jan 13 '20 at 15:06
  • @user253751: It would mean that in C++. It does not mean that in C. – Eric Postpischil Jan 13 '20 at 15:14
  • That is generally what "pass by reference" means across multiple languages. In C, you pass pointers by value. – user253751 Jan 13 '20 at 15:17
  • 1
    @EricPostpischil Pass-by-reference doesn't exist in C neither does it in Java or C#. They are pass-by-value, you pass the **value** of the pointer. See for C language : https://stackoverflow.com/a/2229510/12624874. This is only a simulation of pass-by-reference. – Philippe B. Jan 13 '20 at 15:32
  • @PhilippeB.: Your assertion shows no awareness of the explanation given previously; it merely asserts a statement without explanation. The comment you refer to says “In C, Pass-by-reference is simulated.” But this is not a simulation in the sense of modeling boats using a harbor, which does not actually accomplish any moving of cargo. It is an implementation that actual effects the passing of an object: A pointer to the object is passed by value, and this results in the caller having access to the object. Thus, the object is effectively passed by to the caller. It is passed by way of a thing… – Eric Postpischil Jan 13 '20 at 15:39
  • 1
    … that refers to the object, which is the pointer. **It is in fact an implementation of pass-by-reference.** It is merely one that is implemented by conversion to pointer instead of by some other built-in language feature. The comment you refer to goes on to say “This will be referred to as ‘C style pass-by-reference.’” – Eric Postpischil Jan 13 '20 at 15:40
  • 1
    I'm not trying to argue or anything, but this is as said in the post I linked, "C style" which doesn't mean it is actually "pass-by-reference". As user253751 pointed out, passed by reference means that if you do `b = whatever`, `b` in main will become `whatever` which is not the case in C. "Pass-by-reference" is a global 'concept' (pardon my poor english) which doesn't vary between languages, thus you can only replicate part of it and not how it actually is. My point being that there is no "pass-by-reference" in C, only "pass-by-value". This is an **incomplete** implementation in my opinion. – Philippe B. Jan 13 '20 at 16:07
  • @PhilippeB. I think the concept of pass by reference is satisfied by a pointer (which could just as well have been called a "reference", and indeed translates to some other natural languages in the same way). Some other languages have additional syntactic sugar for references, yes, but I don't think that it's fundamental to the concept, or to what the question here is about… – Arkku Jan 13 '20 at 16:11
  • @Sourav Ghosh, I see. To summarize what you wrote, because an array is being passed into the function, it decays to a pointer to the first element of its array. 22 is being stored, then, in the first element of the b[4] array. After the change is made, the new value of b[4] should be {22, 1, 2, 3}, correct? – Trifonas-Kaoulla Jan 17 '20 at 15:23
3

Array expressions are special in C.

6.3.2.1 Lvalues, arrays, and function designators
...
3 Except when it is the operand of the sizeof operator, the _Alignof operator, or the unary & operator, or is a string literal used to initialize an array, an expression that has type ‘‘array of type’’ is converted to an expression with type ‘‘pointer to type’’ that points to the initial element of the array object and is not an lvalue. If the array object has register storage class, the behavior is undefined.

C 2011 online draft

When you call

set_array(b);

the expression b is converted from type "4-element array of int" to "pointer to int" (int *), and the value of the expression is the address of b[0]. Therefore, what set_array receives is a pointer value (&b[0]), not an array.

6.7.6.3 Function declarators (including prototypes)
...
7 A declaration of a parameter as ‘‘array of type’’ shall be adjusted to ‘‘qualified pointer to type’’, where the type qualifiers (if any) are those specified within the [ and ] of the array type derivation. If the keyword static also appears within the [ and ] of the array type derivation, then for each call to the function, the value of the corresponding actual argument shall provide access to the first element of an array with at least as many elements as specified by the size expression.

ibid.

Basically, in a function definition, any parameter declared as T a[N] or T a[] shall be interpreted as T *a - IOW, the parameter is treated as a pointer, not an array. Your function definition

void set_array(int array[4]){
    array[0] = 22;
}

is treated as though it was written

void set_array(int *array){
    array[0] = 22;
}

So arrays kind-of-sort-of-but-not-really get passed by reference in C. What really happens is that a pointer to the first element of the array gets passed by value. The array parameter in set_array designates a distinct object in memory from b, so any changes you make to array itself have no effect on b (IOW, you could assign a new value to array and have it point to a different object, but this assignment doesn't affect b at all). Instead, what you are doing is accessing elements of b through array.

In picture form:

       +---+
    b: |   | b[0] <---+
       +---+          |
       |   | b[1]     |
       +---+          |
       |   | b[2]     |
       +---+          |
       |   | b[3]     |
       +---+          |
        ...           |
       +---+          |
array: |   | ---------+
       +---+

One practical consequence of this is that sizeof array yields the size of the pointer type int *, not the size of the array, unlike sizeof b. That means you can't count the number of elements in the array using the sizeof array / sizeof array[0] trick. When you pass an array as an argument to a function, you should also pass the size of the array (that is, the number of elements) as a separate parameter unless the array contains a well-defined sentinel value (like the 0 terminator in strings).

Remember that the expression a[i] is defined as *(a + i) - given a starting address a, offset i elements (not bytes!) from that address and deference the result. If a is an array expression, it is first converted to a pointer expression before this computation is done. That's why you can access elements of b through array - array simply stores the address of the first element of b.

In C, all function arguments are passed by value - the formal argument in the function definition and the actual argument in the function call refer to distinct objects in memory, and the value of the actual argument gets copied to the formal argument. Changes to the formal argument are not reflected in the actual argument.

We fake pass-by-reference semantics by passing a pointer to the actual parameter and writing to the dereferenced pointer:

void foo( T *ptr )
{
  *ptr = new_value();  // write a new value to the thing ptr points to
}

void bar( void )
{
  T var;
  foo( &var ); // have foo write a new value to var
}

IOW, writing to *ptr is the same as writing to var. Writing to ptr, OTOH, has no affect outside of foo.

In a true pass-by-reference system (like, say, Fortran) both the formal argument in the function definition and the actual argument in the function call designate the same object (in the "thing that takes up memory and can store values" sense, not the object-oriented "instance of a class" sense), so in those systems any changes to the formal argument are reflected in the actual argument (leading to a classic question on the Hacker Test, "Have you ever changed the value of 4? Unintentionally? In a language other than Fortran?")

John Bode
  • 119,563
  • 19
  • 122
  • 198