为什么将数组typedef变量视为C中的指针

时间:2018-09-17 15:29:05

标签: c

在进行代码审查时,我发现当函数将指针作为参数foo(foo)时,程序员不小心直接传递了typedef对象foo(&foo)。由于某种原因,只要typedef是数组而不是例如,它仍然可以工作。一个结构。有人可以解释为什么吗?请参见以下代码:

#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;
}

3 个答案:

答案 0 :(得分:0)

在C语言中,数组通常会自动转换为指向其第一个元素的指针。 C 2018 6.3.2.1 3说:

  

除非它是 sizeof 运算符或一元运算符的操作数,或者是用于初始化数组的字符串文字,否则其类型为“ 类型为的数组”将转换为类型为“ 要指向的指针”的表达式,该表达式指向数组对象的初始元素,而不是左值。如果数组对象具有寄存器存储类,则该行为是不确定的。

因此,当类型foo_t是三个uint8_t的数组,并且x是类型foo_t的对象时,则在诸如{ {1}},function(x)被自动转换,因此函数调用等效于x

但是,在这种情况下,结果是function(&x[0])成为指向x的指针。参数必须仍然满足在函数声明中对参数类型的调用中有关匹配参数类型的规则。如果声明函数的参数具有指向uint8_t的类型指针(因此它是指向三个foo_t的数组的指针),则编译器应给出警告或错误,指出uint8_t为传递指向function(x)的指针。

在您包含在问题中的代码中没有这样的调用,因此不清楚您在问什么。 uint8_t调用确实包含作为数组的参数,这些参数被转换为指针。允许将这些作为memcpy的参数,因为其参数被声明为memcpyvoid *,并且指向任何类型对象的指针都可以转换为const void *

(此外,作为数组的参数声明会自动调整为指向数组元素的指针。)

答案 1 :(得分:0)

因为foo&foo的值相同,但函数调用中的类型不同。

foo的类型为foo_t,即uint8_t[2]。这是一种数组类型,因此在传递给函数时,它会衰减指向该数组第一个元素的指针change_foo(foo)等效于change_foo(&foo[0]))。

&foo的类型为foo_t*,即uint8_t(*)[2],即指向整个数组的指针。当指向整个数组时,指针中存储的地址就是其第一个元素的第一个字节的地址,这毫不奇怪,它也是数组第一个元素的第一个字节的地址。

因此,调用change_foo(foo)change_foo_p(&foo)编译为相同的机器代码。 change_foo_p()用两个不匹配的指针调用memcpy()时会调用未定义的行为,但是由于它获得的传递的地址与change_foo()相同,因此未定义的行为恰好是您想要的行为

答案 2 :(得分:-2)

在C中定义数组时,实际上存储了数组的起始地址。

int数组[25]; //存储数组的起始地址。例如:0x00ffabcd

array [10] == *(array + 10)//索引运算符用作指针算术。>

请注意,在上面的示例中,编译器知道指针类型,而指针算法知道应该从数组的起始地址跳转到10 * sizeof(int)。在某些情况下,例如当您将数组传递给memset时,可能需要将sizeof运算符的结果提供给指针算法。

我将留下答案,因为评论指出了错误的原因。