首页 > 解决方案 > 将非数组变量的地址传递给声明为“Type ptr[static 1]”的函数参数是否有效?

问题描述

正如这里提到这里这里的一个函数(在 c99 或更高版本中)以这种方式定义

void func(int ptr[static 1]){
    //do something with ptr, knowing that ptr != NULL
}

有一个指向 int 类型指针的参数 ( ptr),编译器可以假设函数永远不会以 null 作为参数调用。(例如,编译器可以优化空指针检查或警告是否使用空指针调用 func - 是的,我知道,编译器不需要做任何这些......)

C17 第6.7.6.3 节函数声明符(包括原型)第 7 段说:

将参数声明为“类型数组”应调整为“指向类型的限定指针”,其中类型限定符(如果有)是在数组类型派生的 [ 和 ] 中指定的那些。如果关键字static也出现在数组类型派生的 [ 和 ] 中,则对于函数的每次调用,对应的实际参数的值应提供对至少与指定元素一样多的数组的第一个元素的访问由大小表达式。

在上述定义的情况下,ptr必须提供对具有至少1元素的数组的第一个元素的访问。因此很明显,参数永远不能为空。

我正在徘徊的是,使用不属于数组的 int 的地址调用这样的函数是否有效。例如,这(根据func上面的定义)在技术上是有效的还是未定义的行为:

int var = 5;
func(&var); 

我知道这实际上永远不会成为问题,因为我所知道的编译器没有区分指向 int 数组成员的指针和指向局部 int 变量的指针。但是考虑到 c 中的指针(至少从标准的角度来看)可能不仅仅是一些具有特殊编译时类型的整数,如果标准中有某些部分,我会徘徊,这使得它有效。

我确实怀疑它实际上是无效的,因为第6.5.6 节加法运算符第 8 段包含:

[...] 如果指针操作数和结果都指向同一个数组对象的元素,或者超过数组对象的最后一个元素,则评估不应产生溢出;否则,行为未定义。[...]

对我来说,对于指向数组元素的任何指针来说,添加1都是有效的操作,而添加1到指向常规变量的指针则是 UB。这意味着,指向数组元素的指针和指向普通变量的指针之间确实存在差异,这将使 UB 上面的代码片段......

6.5.6 节加法运算符第 7 段包含:

出于这些运算符的目的,指向不是数组元素的对象的指针与指向长度为 1 且对象类型作为其元素类型的数组的第一个元素的指针的行为相同。

由于该段落以“为了这些运营商的目的”开头,我怀疑在其他情况下可能会有所不同?


tl;博士;

是否有标准的某些部分指定,指向常规类型变量的T指针和指向长度为 1 的数组(类型数组T[1])的元素的指针之间没有区别?

标签: cfunctionnullstandardsundefined-behavior

解决方案


从表面上看,我认为你有道理。我们并没有真正传递指向数组第一个元素的指针。如果我们在真空中考虑标准,这可能是 UB。

除了您在 6.5.6 中引用的段落之外,标准中没有任何段落将单个对象等同于一个元素的数组。而且不应该,因为这两件事是不同的。当出现在大多数表达式中时,数组(甚至一个元素)会隐式转换为指针。这显然不是大多数对象类型所拥有的属性。

static关键字 in的定义[]提到要传递的指针必须指向至少包含一定数量元素的数组的初始元素。你引用的措辞还有一个问题,怎么样

int a[2];
func(a + 1);

显然,传递的指针不是指向数组的第一个元素。如果我们对 6.7.6.3p7 进行字面解释,那也是 UB。

static关键字放在一边,当函数接受指向对象的指针时,该对象是否是数组的成员(任何大小)仅在一个上下文中很重要:指针算术。

在没有指针算法的情况下,当使用指针访问数组元素或独立对象时,行为没有明显差异。

我认为 6.7.6.3p7 背后的意图是考虑到指针运算。因此,所提到的语义与尝试对传递给函数的指针进行指针运算密切相关。

简单地使用static 1简单地作为有用的习语自然而然地出现了,也许并不是一开始的意图。虽然规范性文本可能会稍作修正,但我认为其背后的意图是明确的。这并不是标准的未定义行为。


推荐阅读