T S*_*T S 6 c null standards function undefined-behavior
如here,here和here以这种方式定义的函数(在c99 或更新版本中)
void func(int ptr[static 1]){
//do something with ptr, knowing that ptr != NULL
}
Run Code Online (Sandbox Code Playgroud)
有一个ptr指向 int 类型指针的参数 ( ) 并且编译器可以假定该函数永远不会以 null 作为参数被调用。(例如,编译器可以优化空指针检查,或者在使用空指针调用 func 时发出警告 - 是的,我知道,编译器不需要执行任何操作......)
C17 第6.7.6.3节函数声明符(包括原型)第 7 段说:
将参数声明为“类型数组”应调整为“指向类型的限定指针”,其中类型限定符(如果有)是在数组类型派生的 [ 和 ] 中指定的那些。如果关键字static也出现在数组类型派生的 [ 和 ] 中,那么对于函数的每次调用,相应实参的值应提供对数组的第一个元素的访问,该元素至少与指定的元素一样多由大小表达式。
在上述定义的情况下,值ptr必须提供对至少具有元素的数组的第一个元素的访问1。因此很明显,该参数永远不能为空。
我在徘徊的是,使用不属于数组的 int 的地址调用这样的函数是否有效。例如,这是(根据func上面的定义)技术上有效还是未定义的行为:
int var = 5;
func(&var);
Run Code Online (Sandbox Code Playgroud)
我知道这实际上永远不会成为问题,因为我所知道的编译器没有区分指向 int 数组成员的指针和指向局部 int 变量的指针。但是考虑到 c 中的指针(至少从标准的角度来看)可能不仅仅是一些具有特殊编译时类型的整数,如果标准中有某个部分,我会徘徊,这使得它有效。
我确实怀疑它实际上是无效的,因为第6.5.6节加法运算符第 8 段包含:
[...] 如果指针操作数和结果都指向同一个数组对象的元素,或者指向数组对象最后一个元素之后的元素,则评估不会产生溢出;否则,行为未定义。[...]
对我来说,这听起来好像任何指向数组元素的指针添加1都是有效的操作,而添加1到指向常规变量的指针则是 UB 。这意味着,指向数组元素的指针和指向普通变量的指针之间确实存在差异,这将使 UB 上方的代码段...
对于这些运算符而言,指向不是数组元素的对象的指针的行为与指向长度为 1 的数组的第一个元素的指针的行为相同,该数组的元素类型为对象的类型。
由于该段落以“为了这些运算符的目的”开头,我怀疑在其他上下文中可能存在差异?
标准的某些部分是否指定了指向常规类型变量的T指针和指向长度为 1 的数组(类型数组T[1])的元素的指针之间没有区别?
从表面上看,我认为你说得有道理。我们实际上并没有传递指向数组第一个元素的指针。如果我们在真空中考虑该标准,这可能是 UB。
除了您在 6.5.6 中引用的段落之外,标准中没有任何段落将单个对象等同于一个元素的数组。也不应该有,因为两者是不同的。当出现在大多数表达式中时,数组(甚至只有一个元素)会隐式转换为指针。这显然不是大多数对象类型所拥有的属性。
static中关键字的定义[]提到,传递的指针必须指向至少包含一定数量元素的数组的初始元素。你引用的措辞还有一个问题,怎么办?
int a[2];
func(a + 1);
Run Code Online (Sandbox Code Playgroud)
显然,传递的指针不是指向数组的第一个元素。如果我们按字面解释 6.7.6.3p7,那也是 UB。
把static关键字放在一边,当函数接受指向对象的指针时,该对象是否是数组(任何大小)的成员仅在一个上下文中重要:指针算术。
在没有指针算术的情况下,使用指针访问数组元素或独立对象时,行为上没有明显的差异。
我认为 6.7.6.3p7 背后的意图是考虑指针算术。因此,所提到的语义与尝试对传递给函数的指针进行指针算术密切相关。
的使用static 1只是作为有用的习语自然而然地出现,也许并不是一开始的意图。虽然规范性文本可能需要稍加修正,但我认为其背后的意图是明确的。这并不意味着标准中未定义的行为。