Home > Software design >  Taking address of fixed-size-array argument seemingly fails. Why?
Taking address of fixed-size-array argument seemingly fails. Why?

Time:07-12

I am playing with example code provided in one of the answers to typedef fixed length array .

The answer only states that the code fails, but does not explain, why. Could anyone provide an explanation?

#include <stdio.h>

typedef int twoInts[2];

void print(twoInts *twoIntsPtr);
void intermediate (twoInts twoIntsAppearsByValue);

int main () {
    twoInts a;
    a[0] = 0;
    a[1] = 1;
    print(&a);
    intermediate(a);
    return 0;
}

void intermediate(twoInts b) {
    printf("im1: %d, %d\n", b[0], b[1]);
    print(&b);
    printf("im2: %d, %d\n", b[0], b[1]);
}

void print(twoInts *c){
    printf("pr: %d, %d\n", (*c)[0], (*c)[1]);
}

Compiling this produces the following warnings:

a.c: In function ‘intermediate’:
a.c:19:11: warning: passing argument 1 of ‘print’ from incompatible pointer type [-Wincompatible-pointer-types]
   19 |     print(&b);
      |           ^~
      |           |
      |           int **
a.c:5:21: note: expected ‘int (*)[2]’ but argument is of type ‘int **’
    5 | void print(twoInts *twoIntsPtr);
      |            ~~~~~~~~~^~~~~~~~~~

And the output is:

pr: 0, 1
im1: 0, 1
pr: 1854416416, 32767
im2: 0, 1

What I can't understand is why the "pr" lines differ. After all - both a and b have type twoInts and both produce same results when index operator ([]) is applied.

========== UPDATE ===========

I now realize that what I'm really interested in is what the actual value (actual bytes) does the argument c has?

Let's say we have the following memory layout:

The original array is located at memory address 150:

m[150]=0
m[154]=1

Argument b is in stack and has (let's say) address 140. My understanding is that it has type int* and therefore probably holds address of first element of the array:

m[140]=150  // argument b

Calling the print() from main() will probably put the argument c at the very same address, but I'm not sure about the value:

m[140]=?    // argument c, print() is called directly from main()

Calling the print() from intermediate() will put argument c at even earlier address, but would the value be 140 - the address of argument b?

m[130]=?    // argument c, print() called from intermediate()

CodePudding user response:

A function parameter that has an array type is adjusted by the compiler to pointer to the array element type.

So this function declaration

void intermediate (twoInts twoIntsAppearsByValue);

is adjusted by the compiler to the declaration

void intermediate ( int * twoIntsAppearsByValue);

Within the function

void intermediate(twoInts b) {
    printf("im1: %d, %d\n", b[0], b[1]);
    print(&b);
    printf("im2: %d, %d\n", b[0], b[1]);
}

there is used this statement

print(&b);

where the argument expression has the type int ** while the corresponding parameter of the function has the type int ( * )[2].

void print(twoInts *c){
    printf("pr: %d, %d\n", (*c)[0], (*c)[1]);
}

These pointer types are not compatible and the compiler shall issue a corresponding message.

In the function intermediate the expression &b points to its local variable (parameter) b.

In the call of the function print in main

print(&a);

the expression &a points to the original array.

Here is a demonstration program that shows the difference.

#include <stdio.h>

typedef int twoInts[2];

int main( void )
{
    twoInts a = { [0] = 0, [1] = 1 };

    twoInts *p1 = &a;

    int *p2 = a;
    int **pp2= &p2;

    printf( "*p1 = %p\n", ( void * )*p1 );
    printf( "*pp2 = %p\n", ( void * )*pp2 );
    printf( "*( int ( * )[2] )pp2 = %p\n", ( void * )*( int ( * )[2] )pp2 );
    printf( "&p2 = %p\n", ( void * )&p2 );
}

The program output might look like

*p1 = 0x7ffef35980c8
*pp2 = 0x7ffef35980c8
*( int ( * )[2] )pp2 = 0x7ffef35980c0
&p2 = 0x7ffef35980c0

So you got the ouput

pr: 1854416416, 32767

because the address of the parameter b was considered as the address of the array itself.

CodePudding user response:

I have played with my program a bit more and discovered two interesting properties:

  1. if I print the pointer that print() function receives then it is (unsurprisingly) differs between two invocations, the second pointer holding smaller address than the first (also somewhat unsurprisingly).
  2. if I remove the address-taking in the intermediate() function and just call print(b); then the pointers become the same and therefore the output also becomes the same:

void intermediate(twoInts b) {
    printf("im1: %d, %d\n", b[0], b[1]);
    print(b);
    printf("im2: %d, %d\n", b[0], b[1]);
}

Output:

pr: 0, 1
im1: 0, 1
pr: 0, 1
im2: 0, 1

While this is not really an answer to my question, this still provides enough insight.

The b seems to be interpreted sometimes as int* and sometimes as int** - all within the same function. Therefore when I was taking address of it, it was effectively producing int***.

So I guess I should be grateful that it didn't segfault at (*c)[1]).

  • Related