0

As the code below, function pointer y, z, b behave the same. Even *x would be understand as data of pointer x and &x would understand as pointer to x.

typedef void (*func)(void);

void x(void){
    printf("asdsag\n");
    return;
}

int main(int argc, char const *argv[])
{
    func y = *x;
    func z = x;
    func b = &x;
    y();
    z();
    b();
    return 0;
}

Now look at this code.

void test(int **p){
    printf("%p\n",p);
    return;
}
int main(int argc, char const *argv[])
{
    int * p[5];
    test(p);
    test(&p);
    return 0;
}

This code print same pointer for all 3 case. Here is asembly after compile above code

main:
    @ args = 0, pretend = 0, frame = 32
    @ frame_needed = 1, uses_anonymous_args = 0
    push    {r7, lr}
    sub sp, sp, #32
    add r7, sp, #0
    str r0, [r7, #4]
    str r1, [r7]
    ldr r3, [r7, #12]
    mov r0, r3
    bl  test
    add r3, r7, #12
    mov r0, r3
    bl  test
    add r3, r7, #12
    mov r0, r3
    bl  test
    movs    r3, #0
    mov r0, r3
    adds    r7, r7, #32
    mov sp, r7
    @ sp needed
    pop {r7, pc}
    .size   main, .-main

It is seem that they take same r7+#12 for all three case.

[Question] For all given information above, what should I understand p,*p,&p in C99?

[Info] This is build with gcc-linaro-7.2.1-2017.11-x86_64_arm-linux-gnueabihf

[Update] Update for @tadman about imcompatible pointer

#include <stdio.h>

void test1(void **p){
    printf("%p\n",p);
    return;
}

void test2(void *p){
    printf("%p\n",p);
    return;
}

int main(int argc, char const *argv[])
{
    void * p[5] = {0};
    test1(p);
    test2(&p);
    return 0;
}

There is no warning even with -Wall Here is the output:

0x7fff5615bb20
0x7fff5615bb20

P/S I was wrong in the first case, so I delete it.

Silver
  • 406
  • 2
  • 12
  • Why not get a [good reference book](https://stackoverflow.com/questions/562303/the-definitive-c-book-guide-and-list) to start? – tadman Nov 18 '20 at 06:16
  • 2
    That second chunk of code should generate a lot of warnings. Try with `-Wall`. Just because it compiles doesn't mean it's *correct*. – tadman Nov 18 '20 at 06:17
  • @tadman I think the answer to be written thoroughly will help most of the guys even knowing or experienced in C. – Soner from The Ottoman Empire Nov 18 '20 at 06:18
  • Not really. The code you have here is just undefined behaviour because you're using `test()` incorrectly. The `*p` and `&p` versions are just nonsense pointers. Anyone who's used C for a while knows what those operators do. – tadman Nov 18 '20 at 06:18
  • @tadman I igone those `#include` thing to shorten the code, and the question is not about is the code correct or not, the question is about how GCC compile and understand the syntax – Silver Nov 18 '20 at 06:20
  • @tadman I do not understand what you mean by undefined behavior in this case. I have compile the code several time in different manner (focusing on size of and type of `p`) and the behavior is the same. That made I do not think that it is undefined behavior – Silver Nov 18 '20 at 06:25
  • 2
    GCC, like many compilers, will try and do what you ask even if it makes no sense. Casting some random `int` into a pointer is fine, so long as you never use the pointer, but that also means it's pointless. Just because the assembly code comes out the same doesn't mean you have defined behaviour, just *consistent* behaviour. There's a huge gulf between that and defined behaviour. – tadman Nov 18 '20 at 06:30
  • @tadman by alot of warning, it only give me -Wincompatible-pointer-types -Wuninitialized which i do not think it is matter here. – Silver Nov 18 '20 at 06:31
  • 2
    Yeah, it kind of matters because only one usage is valid, and the rest are just junk data. **"Incompatible pointer types" is kind of a big deal.** – tadman Nov 18 '20 at 06:32
  • 3
    Using uninitialized data is also kind of a big deal. – John Bollinger Nov 18 '20 at 06:35
  • If it matter, how do you explain the example of function pointer above? they are not junk data, they point to the same pointer, if you not notice that I use "%p" to print the address of it, not the data. which prove that `*p`, `p`, `&p` is a pointer point to same memory. – Silver Nov 18 '20 at 06:37
  • @JohnBollinger not a big deal if I do not use the data. – Silver Nov 18 '20 at 06:38
  • 3
    It depends on what `p` is. If it's a function, then `*p`, `&p` and `p` are all basically the same, as is even `&**&&*&*p` because those operators don't do anything. If it's a primitive like `int` then the operators are a huge deal and change what that represents in a fundamental way. – tadman Nov 18 '20 at 06:38
  • You *do* use the data. You *print* it. – John Bollinger Nov 18 '20 at 06:39
  • 2
    In any case, no explanation is necessary beyond the involvement of undefined behavior. This is one reason why undefined behavior should be avoided. – John Bollinger Nov 18 '20 at 06:40
  • @JohnBollinger "%p" do not print the data, it print the address. – Silver Nov 18 '20 at 06:41
  • 1
    No, @Silver. `%p` prints the *value* of a pointer. If it is a valid pointer then that's the address of *something else*. – John Bollinger Nov 18 '20 at 06:44
  • Note also that for me, your second example does not print the same thing for all three calls, nor would I expect GCC to produce that result. Only the latter two outputs are the same. If you indeed observe the same output three times then that, too, is a reason to avoid undefined behaviors. – John Bollinger Nov 18 '20 at 06:46
  • If this is undefined behavior, it would not scatter through linux kernel, In the code of linux kernel, there is many cast from anytype of pointer to (void *) and thing. For example, file->private_data – Silver Nov 18 '20 at 06:49
  • @JohnBollinger in that case, value of pointer is not what the warning is about ==) – Silver Nov 18 '20 at 06:50
  • @Silver, you're the one asking the question. I suggest you take some time to try to digest the responses, coming as they do from people who know what they're talking about. – John Bollinger Nov 18 '20 at 07:02
  • 1
    `void*` pointers are effectively C "generics" and have far fewer restrictions placed on them. They can't be used as-is, they must be recast back to something, and that's where you assume a lot of responsibility for ensuring that's a valid cast. – tadman Nov 18 '20 at 09:40

2 Answers2

2

In the first case you have a function, like this:

typedef void (*f)(void);

void p(void) {
}

int main(int argc, char const *argv[]) {
    func a = p;
    func b = *p;
    func c = &p;
    a();
    b();
    c();

    return 0;
}

All three of these work because p and &p are the same thing, the & operator on a function is optional and is basically a "do nothing" operator. On clang at least * is also a no-op, which isn't documented but appears to be the case. As such you can do &p or *p or &*p or ****&&**&*p and you get the same thing.

In the second case you have actual data:

void test(int **p) {
}

int main(int argc, char const *argv[]) {
    int *p[5];

    test(p);  // Valid: int** -> int**
    test(&p); // Invalid: Incompatible pointer int*** -> int**
    test(*p); // Invalid: incompatible pointer int* -> int**

    return 0;
}

The first is defined behaviour, you're passing an identical pointer type in so it works. The second two are just junk code. Maybe they'll compile, but the behaviour is undefined. Whatever they do is completely arbitrary.

tadman
  • 208,517
  • 23
  • 234
  • 262
  • At the risk of confusing the OP more, but because not saying anything also could confuse them more, the type of `&p` is `int *(*)[5]`, not `int ***` (and of course not `int **`, either, as you say). – John Bollinger Nov 18 '20 at 06:51
  • please check my updated question – Silver Nov 18 '20 at 07:04
  • @JohnBollinger I'm just reporting *exactly* what clang says. It also talks about that version in another message but the "Invalid" remark is exactly as shown. – tadman Nov 18 '20 at 09:39
  • Clang's diagnostics are very misleading in that regard, then. FWIW, the diagnostics GCC emits for the example code express that type correctly. I don't care so much about the diagnostics, but it is important for people to understand the types involved. `int ***` and `int *(*)[5]`, are not the same, and they behave differently in some important regards (that is, the difference is not negligible). – John Bollinger Nov 18 '20 at 13:03
2

Don't confuse levels of indirection of function pointers and regular pointers. This is a particularly quirky thing about the C standard.

§ 6.3.2.1

Lvalues, arrays, and function designators

...

  1. A function designator is an expression that has function type. Except when it is the operand of the sizeof operator,67) or the unary & operator, a function designator with type "function returning type" is converted to an expression that has type "pointer to function returning type".

Emphasis on-

function designator with type "function returning type" is converted to an expression that has type "pointer to function returning type".

(Mention the correct part of the standard, thanks to @John Bollinger)

Which essentially means a, b, and c are all the same here

func a = p;
func b = *p;
func c = &p;

All of them can be treated like a regular function and be called with (...).

The behavior is much, much different for regular pointers though - as in your next example.

int * p[5];
test(*p);
test(p);
test(&p);

So int* p[5] declares an array of 5 pointers. So the type of p is int*[5], the type of each element in p (such as p[0]) is int*.

So, with test(*p), you're just passing p[0] (because *(p + 0) == p[0] == *p) - which is of type int*. Conversion from int* to int** is undefined and hence that's a big no no.

With test(&p), you're passing a value of type int*(*)[5]. Once again, different levels of indirection - and not on function types - the real rules apply here. The behavior is undefined.

test(p) is fine though, p, as a type int*[5] gets decayed to int** which is a valid argument to test

(Edited to reflect non decayed versions of the pointer types, thanks to thanks to @John Bollinger)

Also since we're talking about undefined behavior so much and in case you are confused. It basically means that the behavior has no well defined result - you might observe something apparently defined on your PC and your environment - but that same behavior is not guaranteed globally.

3.4.3

undefined behavior

behavior, upon use of a nonportable or erroneous program construct or of erroneous data, for which this document imposes no requirements

Chase
  • 5,315
  • 2
  • 15
  • 41
  • 6.3.2.3/8 is not at all why the `a`, `b`, and `c` of the OP's first example are the same. That comes from 6.3.2.1/4: "Except when it is the operand of [... or] the unary & operator, a function designator with type 'function returning type' is converted to an expression that has type 'pointer to function returning type'". – John Bollinger Nov 18 '20 at 07:08
  • And no, the type of the OP's `p` *is not* `int **`. It is `int *[5]` -- an array of 5 pointers to int. Pointers and arrays are not at all the same thing, though expressions of array type are subject to automatic conversion to pointers, much as expressions of function type are (6.3.2.1/3). Consequently, the type of `&p` is not `int ***`, either -- it is `int *(*)[5]`, pointer to array of 5 pointers to int. These differences are distinguishable in C code. – John Bollinger Nov 18 '20 at 07:13
  • @JohnBollinger ahh I see, edited accordingly – Chase Nov 18 '20 at 07:16
  • I updated the 3rd example, please example it, too. – Silver Nov 18 '20 at 07:25
  • 1
    thank you, this point out the confusion of array and pointer, lvalue-rvalue in C. This question point out most of my confusion. I will working on the rest. – Silver Nov 18 '20 at 08:10