C99中关于调整参数的未定义行为

8
我不理解来自C99标准的以下未定义行为:
在函数定义中,已调整的参数类型不是对象类型(6.9.1)。
根据标准,函数的参数在两种情况下会被调整:
- 数组会被调整为指针, - 函数会被调整为指向函数的指针。
在第二种情况下,函数的调整后的参数确实不是对象(就我所知,标准区分对象和函数):
标识符可以表示对象、函数、结构体、联合体的标签或成员...
您能澄清这一点并提供此类UB的示例吗?

1
@VladfromMoscow 不,这个引用是准确的,来自C99 J.2未定义行为。 - Ian Abbott
@VladfromMoscow,文本已按您在C11 J.2中所示进行了更正。 - Ian Abbott
不知道,C99自从以后已经进行了多次修正。目前的表述是函数定义中的调整参数类型不是完整的对象类型 - Jens Gustedt
2
我认为C99并不过时,许多工具和软件仍在使用它。例如(据我所知)MISRA-C——这是业界主要的编码规则框架——适用于C90和C99。 - Guillaume Petitjean
1
就ISO WG14而言,C99已经过时了,C11和C18也是如此(“本[第n]版取消并替换了[第n-1]版”等)。尽管如此,对于工业界而言,C99仍然有用。C11也以同样的方式过时了。 - Ian Abbott
显示剩余8条评论
2个回答

5
第一个来自C标准的引语是不正确的。它听起来像是:
- 函数定义中的调整参数类型不是完整的对象类型(6.9.1)
你遗漏了单词“完整”。
例如,在函数声明中,如果与其定义不处于同一类型,则可以指定不完整的对象类型,如下所示:
void f( size_t, size_t, int [][*] );

在这个函数声明中,第三个参数的声明不是一个完整的对象类型,因为数组元素的大小是未知的。
以下是一个演示程序。
#include <stdio.h>

void f( size_t, size_t, int [][*] );

void f( size_t m, size_t n, int a[][n] )
{
    for ( size_t i = 0; i < m; i++ )
    {
        for ( size_t j = 0; j < n; j++ )
        {
            a[i][j] = n * i + j;
        }
    }
}

void g( size_t, size_t, int [][*] );

void g( size_t m, size_t n, int a[][n] )
{
    for ( size_t i = 0; i < m; i++ )
    {
        for ( size_t j = 0; j < n; j++ )
        {
            printf( "%d ", a[i][j] );
        }
        putchar( '\n' );
    }
}

int main(void) 
{
    size_t m = 2, n = 3;
    int a[m][n];
    
    f( m, n, a );
    g( m, n, a );
    
    return 0;
}

它的输出是

0 1 2 
3 4 5 

在程序中,这两个函数声明如下:

void f( size_t, size_t, int [][*] );

void g( size_t, size_t, int [][*] );

具有不完整对象类型的参数声明。

您可能无法使用这样的声明,因为它在同一时间同时充当定义,例如

void f( size_t m, size_t n, int a[][*] )
{
    // ...
}

由于编译器无法确定在将第三个参数调整为指针后的指针类型,因此该指针将具有不完整的对象类型int(*)[]


如讨论所述,C99报价中没有错误。 - Guillaume Petitjean
@GuillaumePetitjean 这只意味着你所提到的标准存在缺陷。在2010年12月2日的委员会草案ISO/IEC 9899:201x N1548和差异✿✿✿✿✿✿标记以及2018年11月6日的ISO/IEC 9899:2017✿✿ 2x(E) N2310中,这个缺陷已经得到了更新,并且完整的单词被包含在引用中。 - Vlad from Moscow
@GuillaumePetitjean 如果没有“complete”这个词,这句话就没有意义。 - Vlad from Moscow
2
更好的说法是C99版本省略了“complete”这个词,而不是责怪OP。 - Ian Abbott
投诉更多地涉及到来源的归属问题。 - Ian Abbott
显示剩余5条评论

4
如评论所指出,标准中的文本已在C11中得到更正。现在它是这样写的(C11 J.2):
- 函数定义中的调整参数类型不是完整对象类型(6.9.1)。
这更有意义了。但是,我想不出在函数定义的参数中使用不完整对象类型的例子,可以编译而不会出错。我所能想到的就是,也许一些编译器允许未使用的参数具有不完整的对象类型。如评论者@Lundin所指出的那样,附录J是信息性的,并非标准的规范部分。纠正也在标准规范部分的引用章节6.9.1的文本中进行了更改。6.9.1/7句的最后一个从句已经从“结果类型应为对象类型”改为“结果类型应为完整的对象类型”。@Lundin还指出,在函数定义中,调整后的参数类型如果是不完整类型,则是C11 6.7.6.3 / 4 (C99 6.7.5.3 / 4)的约束违规:
- 调整后,在该函数的定义的一部分的函数声明符中的参数类型列表中的参数不得具有不完整的类型。
这是列在“约束”下的,因此需要翻译程序以产生至少一个诊断。

一个函数定义的参数中存在不完整的对象类型,尽管编译时没有错误,但需要将语法正确的参数类型进行调整,以便衰减为完整的对象类型。因此,编译正确的语法是一回事,但实现正确调整参数的编译器则是另一回事,这里讨论的段落在很大程度上针对的是编译器编写者,而不仅仅是用户(C程序员)。 - ryyker
委员会似乎在措辞上遇到了困难,但是通过将其列为UB,他们本质上是否在说反面的意思:“调整后的函数参数必须解析为完整类型”? - ryyker
1
@ryyker 我认为您的评论很有用。 - Ian Abbott
1
@Ian Abbott C11 6.9.1/7 指向规范章节6.7.6.3,其中6.7.6.3/4(约束)指出:“调整后,在函数声明符中的参数类型列表中的参数不得具有不完整的类型。”这段文字也适用于C99。 - Lundin
@IanAbbott: 如果在类型的具体情况对生成的代码没有影响的情况下,某些编译器可以完全愉快地接受具有不完整参数类型的定义,我将不感到惊讶,如果标准的作者们意识到某些代码可能会利用这一点。 UB 的目的之一是识别“符合语言扩展”的领域,而标准的作者们可能不希望禁止实现在可以实际这样做的情况下以这种方式扩展语言。 - supercat
显示剩余7条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接