简体   繁体   中英

Why is a array typedef variable considered a pointer in C

When doing a code review I found that the programmer had accidentally passed a typedef object directly foo(foo) when the function took a pointer as an argument foo(&foo) . For some reason it still works as long as the typedef is an array and not eg a struct. Can someone explain why? See the following code:

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
typedef uint8_t foo_t[3];
typedef uint8_t bar_t[2];
typedef struct __attribute__((packed))
{
    foo_t foo;
    bar_t bar;
} foobar_t;

void change_foo(foo_t foo)
{
    foo_t bar = {1,2,3};
    memcpy(foo, bar, sizeof(foo_t));
}

void change_foo_p(foo_t *foo)
{
    foo_t bar = {1,2,3};
    memcpy(foo, bar, sizeof(foo_t));
}

void change_foobar(foobar_t foobar)
{
    foo_t bar = {1,2,3};
    memcpy(foobar.foo, bar, sizeof(foo_t));
}    

void change_foobar_p(foobar_t *foobar)
{
    foo_t bar = {1,2,3};
    memcpy(foobar->foo, bar, sizeof(foo_t));
}

int main()
{
    printf("Hello, World!\n");
    foobar_t foobar;
    foobar_t foobar2;
    foo_t foo;
    foo_t foo2;
    change_foo(foo);
    change_foo_p(&foo2);
    change_foobar(foobar);
    change_foobar_p(&foobar2);

    // Prints 1101 since the only method not working is change_foobar()
    printf("%d%d%d%d\n", foo[0], foo2[0], foobar.foo[0], foobar2.foo[0]);        
    return 0;
}

In C, an array is often automatically converted to a pointer to its first element. C 2018 6.3.2.1 3 says:

Except when it is the operand of the sizeof 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.

Thus, when the type foo_t is an array of three uint8_t , and x is an object of type foo_t , then, in a function call such as function(x) , x is automatically converted so that the function call is equivalent to function(&x[0]) .

However, in this case, the result is that x becomes a pointer to uint8_t . The argument must still satisfy the rules about matching argument types in the call to parameter types in the function declaration. If the function's parameter were declared to have type pointer to foo_t (so it is a pointer to an array of three uint8_t ), then the compiler should give a warning or error that function(x) is passing a pointer to uint8_t .

No such call appears in the code you included in the question, so it is unclear what you are asking about. The memcpy calls do contain arguments that are arrays, which are converted to pointers. These are permitted as arguments to memcpy because its parameters are declared to be void * or const void * , and pointers to any types of objects may be converted to void * .

(Additionally, a parameter declaration that is an array is automatically adjusted to be a pointer to an array element.)

Because foo and &foo have the same value, but different type inside a function call.

The type of foo is foo_t , which is uint8_t[2] . This is an array type, so it decays to a pointer to the first element of the array when being passed to a function ( change_foo(foo) is equivalent to change_foo(&foo[0]) ).

The type of &foo is foo_t* , which is uint8_t(*)[2] , ie a pointer that points to the entire array. As it points to the entire array, the address that is stored in the pointer, is the address of the first byte of its first element, which unsurprisingly is also the address of the first byte of the array's first element.

Thus, the calls change_foo(foo) and change_foo_p(&foo) compile to the same machine code. change_foo_p() invokes undefined behavior when it calls memcpy() with two non-matching pointers, but since it gets the same address passed in as change_foo() does, the undefined behavior happens to be the one you intended.

In C when you define an array you actually store the starting address of array.

int array[25]; // stores the starting address of array. ex: 0x00ffabcd

array[10] == *(array + 10) // index operator works as pointer arithmetic.>

Note that in the above example compiler knows the pointer type and pointer arithmetic knows that it should jump to 10 * sizeof(int) from the starting address of array. In some situations, like when you pass array to memset, you might need to give sizeof operator result to the pointer arithmetic.

I will leave the answer cause the comments points to why it is wrong.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM