C99 / C11限制类型限定符是否意味着没有定义的函数?

时间:2018-05-17 15:37:15

标签: c language-lawyer c99 c11 restrict

假设我们有一个函数声明,我们无法访问它的定义:

void f(int * restrict p, int * restrict q, int * restrict r);

由于我们不知道如何访问指针,我们无法知道调用是否会触发未定义的行为 - 即使我们传递相同的指针,如6.7.3.1.10中的示例所解释的:< / p>

  

函数参数声明:

void h(int n, int * restrict p, int * restrict q, int * restrict r)
{
    int i;
    for (i = 0; i < n; i++)
        p[i] = q[i] + r[i];
}
     

说明了如何通过两个受限制的指针对未修改的对象进行别名化。特别是,如果ab是不相交的数组,则h(100, a, b, b)形式的调用已定义了行为,因为数组b未在函数h内修改

因此,restrict在这些情况下是多余的,除非作为调用者的提示/注释,除非我们对该函数有更多了解吗?

例如,让我们从标准库中取sprintf(7.21.6.6):

  

概要

#include <stdio.h>
int sprintf(char * restrict s,
     const char * restrict format, ...);
     

描述

     

sprintf函数等效于fprintf,除了输出被写入数组(由参数s指定)而不是流。 (...)

从概要和说明的第一句中,我们知道s将被写入,s是受限制的指针。因此,我们是否可以假设(无需进一步阅读)如下调用:

char s[4];
sprintf(s, "%s", s);

会触发未定义的行为吗?

  • 如果是,那么:sprintf的描述的最后一句是多余的(即使澄清)?

      

    如果在重叠的对象之间进行复制,则行为未定义。

  • 如果没有,那么,反过来说:restrict限定符是多余的,因为描述是实际上让我们知道什么是未定义行为的那个?

3 个答案:

答案 0 :(得分:1)

  

如果是,那么:sprintf的描述的最后一句是多余的(即使澄清)?   如果在重叠的对象之间进行复制,则行为未定义。

int sprintf(char * restrict s,  const char * restrict format, ...);

restrict上的s表示读取和写入仅取决于sprintf()的作用。以下代码执行此操作,读取和写入p1指向的数据作为char * restrict s参数。读/写仅由于直接sprintf()代码而发生,而不是副作用。

char p[100] = "abc";
char *p1 = p;
char *p2 = p;
sprintf(p1, "<%s>", p2);

然而,当sprintf()访问p2指向的数据时,没有restrict。 “如果在重叠的对象之间进行复制,则行为未定义”适用于p2,说p2的数据不得因某些副作用而改变。

  

如果没有,那么,反过来说:限制限定符是多余的,因为描述是实际上让我们知道什么是未定义行为的那个?

restrict这里是编译器实现restrict访问。鉴于“如果发生复制......”规范,我们不需要看到它。

考虑更简单的strcpy()具有相同的“如果在重叠的对象之间发生复制,则行为未定义。”。对于我们这些读者来说,这里是多余的,因为仔细理解restrict(C99中的新内容)并不需要这样做。

char *strcpy(char * restrict s1, const char * restrict s2);

C89(前restrict天)也有strcpy(), sprintf(), ...的这个措辞,因此可能只是C99中strcpy()的遗留超额规格。

我发现type * restrict p最具挑战性的方面是它指的是数据不会发生什么(p数据不会意外地改变 - 只能通过p)。然而,写入p数据会让其他人感到困惑 - 除非他们有restrict

答案 1 :(得分:1)

  • restrict是在C99中引入的。
  • Since we do not know how the pointers will be accessed, we cannot know if a call will trigger undefined behavior
    是。但这是一个信任问题。函数声明是编写函数定义的程序员和使用该函数的程序员之间的契约。记住,一旦在C中,我们只会写void f(); - 这里f是一个带有未指定数量参数的函数。如果你不相信那个编写该功能的程序员,那么没有人愿意也不会使用这些功能。在C中我们传递第一个数组元素的地址,所以看到这样声明的函数,我会假设:编写该函数的程序员给出了如何使用这些指针或函数f使用它们作为指向单个元素的指针的一些描述,不是数组。
    (在这种情况下,我喜欢在函数声明中使用C99 VLA来指定我的函数所期望的数组:void f(int p[restrict 5], int q[restrict 10], int r[restrict 15]);。这样的函数声明与你的完全相同,但你知道什么是内存可以&#t; t重叠。)
  • char s[4]; sprintf(s, "%s", s);会触发未定义的行为吗? 是。复制发生在重叠的对象和限制位置由两个指针访问。

答案 2 :(得分:0)

给出如下的函数签名:

void copySomeInts(int * restrict dest, int * restrict src, int n);

在源和目标重叠[或者甚至它们相等]的情况下希望函数产生定义行为的人需要花费一些额外的努力才能这样做。有可能,例如

void copySomeInts(int * restrict dest, int const * restrict src, int n)
{
  for (int i=0; i<n; i++)
  {
    if (dest+i == src)
    {
      int delta = src-dest;
      for (i=0; i<n; i++)
        dest[i] = dest[delta+i];
      return;
    }
    if (src+i == dest)
    {
      int delta = src-dest;
      for (i=n-1; i>=0; i--)
        dest[i] = src[delta+i];
      return;
    }
  }
  /* No overlap--safe to copy in normal fashion */
  for (int i=0; i<n; i++)
    dest[i] = src[i];
}

因此,生成调用copySomeInts的代码的编译器无法对其行为做出任何推断,除非它实际上可以看到定义copySomeInts而不仅仅是签名。

虽然restrict限定符并不意味着函数无法处理源和目标重叠的情况,但是他们会建议这种处理可能比没有限定符时更复杂。这反过来表明,除非有明确的文件承诺处理这种情况,否则不应期望函数以定义的方式处理它。

请注意,在源和目标重叠的情况下,实际上不使用src指针或从它派生的任何内容来寻址存储。如果src+i==destdest+i==src,这意味着srcdest都会识别同一数组的元素,因此src-dest只能表示其索引的差异。 src上的const和restrict限定符意味着在执行函数期间可以以任何方式修改使用从src派生的指针访问的任何内容,但该限制仅适用于访问的内容使用从src派生的指针。如果实际上没有使用这样的指针访问任何内容,则限制是空的。