6

I encountered this strange behavior in C.

This code gives me the compile warning:

expected 'double **' but argument is of type 'double (*)[2]'

What is the problem? An array of doubles is a pointer to double, right?

BTW, if I send the array itself, the assignment doesn't affect the values and I don't know why.

#include <stdio.h>

void set1(double **x)
{
    (*x)[0] = (*x)[1] =1.0;
}

int main()
{
    double x[2];
    set1(&x);
    printf("%d\n%d\n",x[0],x[1]);
}
Anass Alzurba
  • 63
  • 1
  • 3
  • "If I send the array itself..." - can you show the code for this? – Oliver Charlesworth Aug 12 '17 at 09:39
  • Why are you passing the address of array instead of passing the array itself? – Tanveer Badar Aug 12 '17 at 09:50
  • 1
    _an array of doubles is a pointer to double, right?_ an array of doubles _decays_ into a pointer to `double` when passed, buts thats not the case for 2D arrays, `double **x` says nothing about the number of elements of the array (2) – David Ranieri Aug 12 '17 at 09:59
  • 1
    Arrays are not pointers, and pointers are not arrays. In particular, a pointer to an array is not a pointer to a pointer. – molbdnilo Aug 12 '17 at 10:06
  • There's no need for further indirection. passing your array to a function *already* adjusts the type to *pointer to double*, as used in JohanBoule's answer. I elaborated a bit on the topic [in this answer](https://stackoverflow.com/a/45582206/2371524). –  Aug 12 '17 at 10:09
  • Arrays in C are always passed by reference. – J...S Aug 12 '17 at 10:16
  • 2
    @J...S **nothing** in C is passed *by reference*. And there's just **no way to pass arrays**. An array type is adjusted to a pointer type in a function declaration, what's passed is a pointer to the first array element (of course, *by value*. You can modify that pointer in the function.) –  Aug 12 '17 at 10:22
  • OT: Printing a `double` using the conversion specifier for an `int` provokes Undefined Behaviour. Do not do this. Use `f` to print a `double`. No need to use the `l` length modifier, as shown in various answers to your question. As opposed to `scanf()`, the `printf()`-family of functions expects a `double` for an `f`. A `float` is promote to a `double` when being passed to a variadic function. – alk Aug 12 '17 at 15:59
  • @alk "*No need to use the `l` length modifier*" <- worse than that, citing "the standard": *If a length modifier appears with any conversion specifier other than as specified above, the behavior is undefined.* (and `l` doesn't mention anything about `f`) -- In short, `%lf` in a `printf()` format string invokes UB. –  Aug 12 '17 at 16:23
  • @FelixPalmen: Thanks for narrowing this down! – alk Aug 12 '17 at 16:26
  • @FelixPalmen `%lf` is correct for a `double` argument as of C99, maybe you are looking at an old version of the standard – M.M Aug 19 '17 at 12:20
  • @M.M I'm looking at C11. It's correct with `scanf()`, not with `printf()`. –  Aug 19 '17 at 12:33
  • @FelixPalmen take a closer look at 7.21.6.1/7 which defines the behaviour of `l` when followed by `f` – M.M Aug 19 '17 at 13:24
  • @M.M indeed I didn't see this last sentence. Maybe you're right and I looked at C89 by accident -- it is missing there. –  Aug 19 '17 at 13:56

4 Answers4

4

For starters according to the C Standard the function main without parameters shall be declared like

int main( void )

To output objects of type double you should use at least the conversion specifier %f instead of %d. Otherwise the function printf has undefined behavior.

Now about pointers.

If you have an object of type T where T is some type specifier sequence as for example

T x;

then a pointer to the object will have type T *

So let's write your program using this abstract type T.

#include <stdio.h>

void set1( T *x )
{
    //...
}


int main(void) 
{
    T x;

    set1( &x ) ;

    // ...

    return 0;
}

The program will not compile because the type T till now is unknown. But what is the type T for the original program?

It can be defined the following way

typedef double T[2];

Now if to add this typedef to the program then it will compile.

#include <stdio.h>

typedef double T[2];

void set1( T *x )
{
    //...
}


int main(void) 
{
    T x;

    set1( &x ) ;

    // ...

    return 0;
}

So what is the type of the function parameter if to use the expression &x as its argument?

Its type is double ( *x )[2]. It is not the same as double ** and there is no implicit conversion between these two types of pointers. That is pointers of these types are incompatible.

Returning to your original program in this case it will look like

#include <stdio.h>

void set1( double ( *x )[2] )
{
    (*x)[0] = (*x)[1] = 1.0;
}


int main(void) 
{
    double x[2];

    set1( &x ) ;

    printf( "%f\n%f\n", x[0] , x[1] );

    return 0;
}

If you want that the function parameter indeed had type double ** then the function argument has to be specified as it is shown in the program below

#include <stdio.h>

void set1( double **x )
{
    (*x)[0] = (*x)[1] = 1.0;
}


int main(void) 
{
    double x[2];
    double *p = x;

    set1( &p ) ;

    printf( "%f\n%f\n", x[0] , x[1] );

    return 0;
}

In this program the pointer p of the type double * points to the first element of the array and the address of the pointer of the type double ** is passed to the function.

However to change the elements of the array it is enough to declare the parameter as having the type double * because in this case the elements of the array are already passed indirectly by using a pointer.

#include <stdio.h>

void set1( double *x )
{
    x[0] = x[1] = 1.0;
}

int main(void) 
{
    double x[2];

    set1( x ) ;

    printf( "%f\n%f\n", x[0] , x[1] );

    return 0;
}
Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
  • thanks, but still I have a problem understanding why should I assign the array to pointer then give the address of the pointer? – Anass Alzurba Aug 12 '17 at 12:30
  • @AnassAlzurba The parameter has the type double **. The pointer p has the type double *. So if to use a pointer to the pointer p you will get the required type of the function parameter double **. – Vlad from Moscow Aug 12 '17 at 13:43
  • @AnassAlzurba That is if the function requires an argument of the type pointer to pointer to double then you have to pass a pointer to pointer to double instead of a pointer to an array that has a different type. – Vlad from Moscow Aug 12 '17 at 14:15
2

Instead of sending &x you should only send x in the function. x indicates the address of the first data in the array, and hence the starting address of the array itself. What you should do is:

void set1(double *x)
{
    x[0] = x[1] =1.0;
}

int main()
{
    double x[2];
    set1(x);
    printf("%d\n%d\n",x[0],x[1]);
}

You're problem arises because you set the type of the argument as a pointer to a pointer to a double value but sent a pointer to an array of double values of length 2 instead.

Debanik Dawn
  • 797
  • 5
  • 28
1

There is no need for pointer syntax at all. You can simplify the code like this :

#include <stdio.h>

void set1(double x[2])
{
    x[0] = x[1] = 1.0;
}

int main(void)
{
    double x[2];
    set1(x);
    printf("%f\n%f\n", x[0], x[1]);
}
Johan Boulé
  • 1,936
  • 15
  • 19
  • In the context of a function's parameter definition this `T var[some dimension]` is the same as `T * var`. – alk Aug 12 '17 at 15:52
  • @alk Yes, it ends up generating the same code, although the array syntax has a self-documenting nature and gives a chance for some static analysis that may warn you when you use out-of-range indexes. I don't see a good reason to make the code more obscure than necessary. – Johan Boulé Aug 12 '17 at 15:58
  • Thanks, I adjusted the printf. – Johan Boulé Aug 12 '17 at 16:04
0

If you explicitly convert the type in the function call as follows set1((double **)&x); the warning will go away

DeLorean88
  • 547
  • 2
  • 5
  • 15