代码之家  ›  专栏  ›  技术社区  ›  Johannes Schaub - litb

最后命名的参数不是函数或数组?

  •  16
  • Johannes Schaub - litb  · 技术社区  · 15 年前

    这个问题是关于vararg函数,以及它们的最后一个命名参数,在省略号之前:

    void f(Type paramN, ...) {
      va_list ap;
      va_start(ap, paramN);
      va_end(ap);
    }
    

    我在C标准中阅读,发现以下限制 va_start 宏:

    参数parmn是函数denition中变量参数列表中最右边的参数的标识符(在…之前的参数)。如果参数parmn是用register存储类、函数或数组类型声明的,或者是用与应用默认参数提升后生成的类型不兼容的类型声明的,则该行为是取消绑定的。

    我想知道为什么下面的代码没有定义行为

    void f(int paramN[], ...) {
      va_list ap;
      va_start(ap, paramN);
      va_end(ap);
    }
    

    并没有为以下内容定义

    void f(int *paramN, ...) {
      va_list ap;
      va_start(ap, paramN);
      va_end(ap);
    }
    

    宏可以通过纯C代码实现。但是纯c代码无法确定是否 paramN 被声明为数组或指针。在这两种情况下,参数的类型都被调整为指针。函数类型参数也是如此。

    我想知道:这种限制的理由是什么?当这些参数调整在内部就位时,一些编译器在实现这一点上有问题吗?(C++中声明了同样的未定义行为-所以我的问题是关于C++ + AsWess)。

    5 回复  |  直到 13 年前
        1
  •  6
  •   Michael Burr    15 年前

    对寄存器参数或函数参数的限制可能如下:

    • 不允许使用 register 存储类。
    • 函数指针有时与对象指针大不相同。例如,它们可能大于指向对象的指针(不能可靠地将函数指针转换为对象指针并再次返回),因此向函数指针的地址添加一些固定数可能不会使您进入下一个参数。如果 va_start() 和/或 va_arg() 通过在 paramN 函数指针比对象指针大,计算结果会为对象指定错误的地址 VAGAR-() 返回。这似乎不是实现这些宏的好方法,但可能有平台拥有(甚至需要)这种实现。

    我想不出防止数组参数会有什么问题,但是PJ Plauger在他的书《标准C库》中这样说:

    对中定义的宏施加的一些限制 <stdarg.h> 似乎不必要的严厉。对于某些实现,它们是。每一个都被引入,但是,以满足至少一个严重的C实现的需要。

    我想很少有人比plauger更了解c库的内部和外部。我希望有人能用一个实际的例子来回答这个特定的问题,我认为这将是一个有趣的琐事。

    新信息:


    “国际标准编程语言的基本原理-c”这样说 VAX START() :

    这个 parmN 论证 va_start 旨在帮助实现者编写 一致性的定义 VAX启动 宏完全用C语言编写,甚至使用C89之前的编译器(例如,通过获取参数的地址)。声明的限制 帕尔蒙 参数遵循允许此类实现的意图,因为如果参数的声明不满足这些限制,则对参数名应用&运算符可能不会产生预期的结果。

    这并不能帮助我限制数组参数。

        2
  •  3
  •   Pavel Minaev    15 年前

    它不是未定义的。请记住,当参数声明为 int paramN[] ,实际参数类型仍将衰减为 int* paramN 立即(在C++中是可见的,例如,如果您应用 typeid paramN )

    我必须承认,我不确定规范中的这个位是什么用途,因为首先不能有函数或数组类型的参数(因为它们会导致指针衰减)。

        3
  •  2
  •   Martin v. Löwis    15 年前

    我找到了另一个相关的引语, from Dinkumware .

    最后一个参数不能有 注册存储类,它必须 具有未被 翻译。它不能有:

    * an array type
    * a function type
    * type float
    * any integer type that changes when promoted
    * a reference type [C++ only]
    

    很显然,问题是参数的传递方式与声明方式不同。有趣的是,他们也禁止浮动和空头,尽管这些应该得到标准的支持。

    作为一个假设,可能是一些编译器在执行 sizeof 在这些参数上是正确的。可能是因为

    int f(int x[10])
    {
            return sizeof(x);
    }
    

    某些(错误的)编译器将返回 10*sizeof(int) ,从而打破了 va_start 实施。

        4
  •  1
  •   j_random_hacker    15 年前

    我只能猜测 register 有一些限制是为了简化库/编译器的实现——它消除了让他们担心的特殊情况。

    但我对数组/函数的限制一无所知。如果它只在C++标准中,我会冒昧地猜测,存在一个模糊不清的模板匹配场景,其中一个类型的参数之间存在差异。 T[] 和类型 T* 有区别,正确处理会使事情复杂化 va_start 但由于这一条款也出现在C标准中,显然排除了这种解释。

    我的结论是:标准的疏忽。可能的场景:一些标准C编译器实现的类型为 t[] T* 与此不同的是,c标准委员会的编译器发言人在标准中添加了上述限制;该编译器后来变得过时,但没有人觉得这些限制有足够的强制力来更新标准。

        5
  •  1
  •   Lightness Races in Orbit    13 年前

    C++ 11表示:

    [n3290: 13.1/3]: [..]参数声明仅在 指针*与数组[]是等价的。也就是说,数组 声明被调整为指针声明。[…]

    C99也是:

    [C99: 6.7.5.3/7]: 将参数声明为类型为数组的数组应调整为指向 类型,其中类型限定符(如果有)是 数组类型派生。[…]

    你说:

    但是纯c代码无法确定是否 paramN 被声明为数组或指针。在这两种情况下,参数的类型都被调整为指针。

    对,所以你给我们看的两段代码没有区别。都有 帕拉姆 声明为指针;实际上根本没有数组类型。

    那么,在ub方面,为什么两者会有区别呢?

    你引用的那篇文章…

    参数parmn是函数denition中变量参数列表中最右边的参数的标识符(在…之前的参数)。 如果参数parmn是用register存储类、函数或数组类型或与应用默认参数提升后生成的类型不兼容的类型声明的,则行为是未定义的。

    …适用于 也不 ,正如所料。