【问题标题】:Why don't pointer to VLA function parameters deduce their size automatically and is there currently any good usage of them?为什么指向 VLA 函数参数的指针不自动推断它们的大小,目前它们有什么好的用法吗?
【发布时间】:2015-04-07 21:34:34
【问题描述】:

据我了解,每个 VLA 都有其大小的隐藏变量,sizeof 运算符可以“获取”该值。我在这里没有得到的是指向函数参数中使用的 VLA 的指针——为什么它们的大小不自动推断并存储在这个隐藏变量中——为什么我们应该明确地提供它。在这种情况下,既然我们已经有了“指向未知大小数组的指针”类型,为什么还要使用它呢?

我的意思是:

void func(size_t, int (*)[*]); //function accepting pointer to VLA

void func_1(size_t, int (*)[]); //function accepting pointer to array of unknown bound

void func(size_t sz, int (*parr)[sz]) //implementation of 'func'
{
    printf("%lu", sizeof(*parr) / sizeof(int));

    printf("%lu", sz);
}

void func_1(size_t sz, int(*parr)[]) //implementation of 'func_1'
{
    //printf("%lu", sizeof(*parr) / sizeof(int)); //error: invalid application of 'sizeof' to an incomplete type 'int []'

    printf("%lu", sz);
}

正如我所见,使用“func”而不是“func_1”的唯一好处是“sizeof”运算符将返回“sz”初始值的副本。

上述函数的示例用法:

int main()
{
    size_t sz = 3;

    int arr[sz];

    func(sizeof(arr) / sizeof(int), &arr);

    func_1(sizeof(arr) / sizeof(int), &arr);

    return 0;
}

为什么不能隐式分配指向 VLA 参数的指针的大小?这至少可以很好地利用语法:

void func(int (*parr)[*]) // size copied from function argument
{
    printf("%lu", sizeof(*parr) / sizeof(int));

    printf("%lu", sz);
}

然后像这样调用函数:

int main()
{
    size_t sz = 3;

    int arr[sz];

    func(&arr);

    return 0;
}

将导致值为“3”的数组隐藏大小变量作为隐藏参数传递给“func”,创建类似于使用当前语法实例化先前的“func”并使用“sizeof”运算符传递数组的代码。

如果你足够好奇将建议的语法编译到任何 Clang 编译器中 - 你会得到一个复活节彩蛋 (;.

【问题讨论】:

  • 没有任何“隐藏变量”。 sizeof 在编译时计算。见stackoverflow.com/questions/2615203/…
  • 对不起,但这种情况不同。这不是普通类型,而是 VA 类型。
  • 我会在你编辑后再说一遍。调用func(&arr); 将不知道有多少数组元素,甚至不知道它是数组还是单个值。没有隐藏大小,它只是一个指针。
  • 但是 'sizeof(arr)' 可以。在“VA”的情况下,主要是通过隐藏变量来实现它。我闻到你不知道类型系统是什么。
  • 我现在会停止评论,希望能遇到一些受过良好教育的 C 程序员(他们已经阅读了 ISO 标准 - 最好的)。

标签: c language-lawyer c11


【解决方案1】:

可变长度数组 (VLA) 的大小与类型相关,而不是与对象相关。

例如,给定:

size_t n = 41;
int vla[n+1];
printf("sizeof vla = %zu\n", sizeof vla);

编译器创建一个初始化为n+1 的匿名对象,我将其称为_anon,并定义了一个类型int[_anon],它是数组对象vla 的类型。 (请注意,稍后更改n 的值不会更改_anon 的值或VLA 类型的大小。

你也可以写:

size_t n = 41;
typedef int vla_t[n+1];
vla_t vla;

sizeof 应用于表达式(在这种情况下,恰好是一个对象名称)会产生该表达式类型的大小。

func 内部,vla 的类型是不可见的,因此无法获取其大小。 parr 是一个指向int 数组的指针未指定大小int[](未指定大小的数组)和int[n+1](长度为n+1 的可变长度数组)类型兼容,但类型不同。

【讨论】:

  • 我不知道为什么,但我认为'parr'是指向'func'内部的VLA的指针,而不是'指向未指定大小的int数组的指针'。
【解决方案2】:

可变长度数组的长度只能在本地知道(因为它必须是,以便堆栈分配存储并提供sizeof 语义)。当它传递给函数时,它只是一个常规的数组类型,因此必须显式传递大小。

【讨论】:

  • 那么 VLA 参数类型的建议是什么。就像现在一样 - 我看不出它们有什么好的用途。
  • 它非常有限......它允许函数声明对其 VLA 类型的长度含糊其辞,同时允许定义根据实际参数指定它。它本身不会导致大小被传递。
  • 所以基本上它只允许某个值的隐式副本,稍后可以由 sizeof 运算符接收。我是这样看的。
  • 是的。我的阅读是,该功能的设计旨在避免以任何方式使 ABI 复杂化。从调用约定的角度来看,VLA 参数是非实体。唯一需要的支持是函数本身的代码生成。
  • @Sneftel 我不太明白你的意思是什么?更重要的是,我们为什么要使用指向 VLA 的指针而不是指向未知大小数组的指针?
【解决方案3】:

在一个令人讨厌的可怕组合中,几十年前就应该废除,但现在依赖于太多代码而无法删除,C 编译器将数组类型的所有函数参数视为等同于指向-的指针的参数元素类型,是否指定了数组大小。

定义一种语法可能不会有任何重大的技术困难,通过该语法可以向函数传递任意大小的数组并自动知道它们有多大。不幸的是:

  1. 目前将数组传递给函数意味着传递指向第一个元素的指针;添加一个新语法意味着其他东西会造成比它解决的更多的混乱。

  2. 如果现有代码接收一个指针和一个整数,并将该指针视为标识指定长度数组的第一个元素,则将该“数组”传递给需要“新样式”数组的方法可能很棘手。

第二个问题可以通过添加语法将指针/长度组合转换为“已知大小的数组”来解决,该数组可以传递给接受此类内容的方法。然而,前一个问题可能无法解决,除非或直到 C 被一种不专注于指针“优化”的语言取代,甚至不再相关。

【讨论】:

  • @FISOCPP:int*[] 类型的参数等效于int** 类型之一;在 * 周围添加括号不会改变这一点。虽然可以声明 VLA 变量,并且理论上可以声明 VLA 参数,但没有 VLA 类型之类的东西,也没有指向 VLA 的指针(不同于指向其第一个元素的指针)。
  • 不是的。来吧 - ISO 书呆子 - 你在哪里?
  • 给定int n = 42;int[n] 是 VLA 类型,int (*)[n] 是指向 VLA 的类型,就像 int (*)[42] 是指向数组的类型一样。问题(嗯,一个问题)是 VLA 只能在块或原型范围内定义;您不能定义 VLA 对象并将其地址传递给知道其类型的函数。
  • @KeithThompson:这些指针令人困惑(我重申我关于笨拙的声明);我想念 Turbo Pascal:^Array of IntegerArray of ^Integer。无论如何,您能否引用“类型”的当前定义?我很想知道它是如何变化的。我也很好奇所有这些东西如何与 typedef 和参数传递交互,以及有多少 C 程序员真正理解所有规则。我从事 C 程序员已有 25 多年了,但我仍然不断发现我从未知道的奇怪事物。
  • @supercat:N1570是C标准的最新公开草案;第 6.2.5 节涵盖类型。 comp.lang.c FAQ 也是一个很好的资源。
猜你喜欢
  • 2018-01-22
  • 1970-01-01
  • 2012-08-13
  • 1970-01-01
  • 1970-01-01
  • 2019-08-17
  • 2010-12-18
  • 2010-11-22
  • 2021-01-25
相关资源
最近更新 更多