10

我对 C99 的可变修改类型系统产生了浓厚的兴趣。这个问题是受这个启发的。

检查这个问题的代码,我发现了一些有趣的东西。考虑这段代码:

int myFunc(int, int, int, int[][100]);

int myFunc(int a, int b, int c, int d[][200]) {
    /* Some code here... */
}

这显然不会(也不会)编译。但是,这段代码:

int myFunc(int, int, int, int[][100]);

int myFunc(int a, int b, int c, int d[][c]) {
    /* Some code here... */
}

编译时甚至没有警告(在 gcc 上)。

这似乎意味着可变修改的数组类型与任何非可变修改的数组类型兼容!

但这还不是全部。您会期望变量修改的类型至少会打扰使用哪个变量来设置其大小。但它似乎没有这样做!

int myFunc(int, int b, int, int[][b]);

int myFunc(int a, int b, int c, int d[][c]) {
    return 0;
}

也编译没有任何错误。

所以,我的问题是:这是正确的标准化行为吗?

此外,如果可变修改的数组类型真的与任何具有相同维度的数组兼容,这是否意味着令人讨厌的安全问题?例如,考虑以下代码:

int myFunc(int a, int b, int c, int d[][c]) {
    printf("%d\n", sizeof(*d) / sizeof((*d)[0]));
    return 0;
}

int main(){
    int arr[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    myFunc(0, 0, 100, &arr);

    return 0;
}

编译并输出 100,没有错误或警告,什么都没有。在我看来,这意味着即使您通过 严格检查数组的大小,也可以轻松进行越界数组写入sizeof而不是进行一次强制转换,甚至打开所有警告!还是我错过了什么?

4

2 回答 2

4

C99,第 6.7.5.2 节似乎是给出相关规则的地方。尤其,

第 6 行:

对于要兼容的两个数组类型,两者都应具有兼容的元素类型,并且如果两个大小说明符都存在并且都是整数常量表达式,则两个大小说明符应具有相同的常量值。如果在要求它们兼容的上下文中使用这两种数组类型,则如果这两个大小说明符的计算结果不相等,则为未定义行为。

以前的,现在已删除的答案也引用了第 6 行。对该答案的评论认为,第二句受第一句末尾的条件限制,但这似乎不太可能阅读。该部分的示例 3 可以澄清(摘录):

int c[n][n][6][m];
int (*r)[n][n][n+1];
r=c;   // compatible, but defined behavior only if
       // n == 6 and m == n+1

这似乎与问题中的示例相当:两种数组类型,一种具有恒定维度,另一种具有相应的可变维度,并且需要兼容。当运行时变量维度与编译时常量维度不同时,行为未定义(示例 3 中的每个注释和 6.7.5.2/6 的一个合理阅读)。无论如何,未定义的行为难道不是您所期望的吗?不然为什么要提这个问题?

假设我们可以同意当这种不匹配发生时行为是未定义的,我观察到编译器通常不需要识别未定义或可能未定义的行为,也不需要发出任何类型的诊断,如果它们确实识别出这样的。在这种情况下,我希望编译器能够警告可能未定义的行为,但它必须成功编译代码,因为它在语法上是正确的并且满足所有适用的约束。请注意,能够警告此类使用的编译器默认情况下可能不会这样做。

于 2015-02-18T19:30:30.703 回答
-1
#include <stdio.h>

void foo(int c, char d[][c])
{
  fprintf(stdout, "c = %d; d = %p; d + 1 = %p\n", c, d, d + 1);
}

int main()
{
  char x[2][4];
  char y[3][16];
  char (*z)[4] = y;  /* Warning: incompatible types */

  foo(4, x);
  foo(16, y);
  foo(16, x);        /* We are lying about x. What can / should the compiler / code do? */
  foo(4, y);         /* We are lying about y. What can / should the compiler / code do? */

  return 0;
}

输出:

c = 4; d = 0x7fff5b295b70; d + 1 = 0x7fff5b295b74
c = 16; d = 0x7fff5b295b40; d + 1 = 0x7fff5b295b50
c = 16; d = 0x7fff5b295b70; d + 1 = 0x7fff5b295b80
c = 4; d = 0x7fff5b295b40; d + 1 = 0x7fff5b295b44

因此, foo() 确实动态地计算出基于 c 将 d 推进多远,正如您的代码所展示的那样。

但是,编译器通常不可能静态确定您是否/何时错误地调用 foo()。似乎如果你这样做,那么编译器会说“好的,我允许你传递你想要的任何东西作为 d,只要它的类型是一个双索引的字符数组。指针 d 上的操作将被确定c. 祝你好运!”

也就是说,是的,编译器通常不能对这些类型的参数进行静态类型检查,因此几乎可以肯定该标准并不要求编译器捕获所有可能静态确定类型不兼容的情况。

于 2015-02-18T19:02:50.010 回答