0

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()
  • The typedef (as is the case 98% of the time) is confusing things. If you write `void intermediate(int *b)` and `void intermediate2(int c[2])`, what is the difference? What is the type of `c`? – William Pursell Jul 11 '22 at 16:42
  • Alexey Nezhdanov, for more clarity, print the pointer: `printf("im1: %d, %d\n", b[0], b[1]);` --> `printf("im1: %p, %d, %d\n", (void*)b, b[0], b[1]);` and `printf("pr: %d, %d\n", (*c)[0], (*c)[1]);` --> `printf("pr: %p %p %d, %d\n", (void*) c, (void*) *c, (*c)[0], (*c)[1]);`. – chux - Reinstate Monica Jul 11 '22 at 17:52

2 Answers2

2

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.

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
  • Thank you! You seem to imply that the runtime value of argument `c` is the address of the first element of the array (when called correctly) - did I understand you right? I have now updated my question with a mocked example of address space to illustrate the idea - so I expect the argument `c` to contain address of the array (150) when called directly from `main()` and address of argument `b` (140) when called the second time. Is that correct? – Alexey Nezhdanov Jul 11 '22 at 17:43
  • @AlexeyNezhdanov The values of expressions a and &a are the same but has different types. Calling the function print in main you are passing the address of the array. Calling the function print inside another function using the expression &b you are passing the address of the pointer b. – Vlad from Moscow Jul 11 '22 at 17:52
0

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]).

  • C typedefs do not change the behavior of C arrays passed as a parameter to a function. Arrays passed to a function "decay" to a pointer to the first element of the array. The size of the array is not implicitly passed when an array pointer is passed to a function. – Jim Rogers Jul 11 '22 at 16:58
  • This size obviously won't be passed so it is unknown at runtime. However I expect the compiler to know the size and that's why I wanted to use fixed-size array in the first place. – Alexey Nezhdanov Jul 11 '22 at 17:37