C语言中的常量数组类型,标准存在缺陷?

16

C99规范的第6.7.3.8段规定:

如果数组类型的说明中包含任何类型限定符,则元素类型是被限定的,而不是数组类型。如果函数类型的说明中包含任何类型限定符,则行为是未定义的。

解释(逻辑页面87,物理页面94)中,给出了将平面指针强制转换为(可变长度)数组指针的示例。

void g(double *ap, int n)
{
    double (*a)[n] = (double (*)[n]) ap;
    /* ... */ a[1][2] /* ... */
}

当函数内未修改数组ap时,应将其标记为const。但在代码中的转换操作不应该被保留。
void g(const double *ap, int n)
{
    const double (*a)[n] = (const double (*)[n]) ap;
    /* ... */
}

由于(根据6.7.3.8)它应用于目标元素而不是目标本身,因此不保留“const”限定符,这意味着编译器会 rightly 抱怨(如果给出适当的标志(GCC 的“-Wcast-qual”)。 在C语言中没有办法表示“const”数组类型,但这种转换非常有用且“正确”。“-Wcast-qual”标志可用于识别数组参数的误用,但是假阳性会阻止其使用。请注意,索引“a [i] [j]”比“ap [i * n + j]”更易读,并且对于许多编译器而言,产生更好的机器代码,因为前者允许一些整数算术被提升出内部循环而不需要进行更多分析。

编译器是否应将其视为特殊情况,有效地从元素中提取限定符以确定给定转换是否删除限定符,还是应修改规范? 对于数组类型未定义赋值,因此将限定符始终应用于数组类型而不仅仅是元素是否会有影响,与6.7.3.8相反?


~(逻辑页N ≡ 物理页N) 时,它让我感到很烦。 - Lightness Races in Orbit
3个回答

6
这是一个已知的问题,在过去的10年中已经在comp.std.c上讨论了多次。底线是你提出的特定情况目前在标准C中不合法;你需要删除限定符或者避免使用指向数组的指针来引用数组中的有资格元素。
如果你认为自己有一个克服这个问题的好主意,可以将其发布到news:comp.std.c进行讨论。如果其他人同意这是一个好主意,你或其他人可以提交一个缺陷报告来改变行为(有几个委员会成员经常访问comp.std.c,因此在提交之前从潜在审查DR的人那里获得反馈是有用的)。我认为你的提议让限定符影响数组本身可能会有一些问题,但我需要再仔细考虑一下。

1
C99添加了一些功能,使C语言在数值计算方面更具吸引力,特别是VLA和restrict。如果编译器能够确定强制转换实际上是安全的,那么就不需要使用标准寻址方式了。 - Jed
一个问题可能是,如果数组由其元素限定符限定 - 那么对于rvalue数组该怎么办?Rvalues没有限定符,但如果是这样,并且rvalue数组具有const元素类型,则在这些情况下,元素类型将不会影响数组限定符。这非常恶心,实际上我们在C ++中也有类似的情况:一些编译器(基于EDG的编译器)从数组元素中剥离限定符,如果数组是rvalue:struct A { char const c[5]; A():c() { } }; int main() { A().c[0] = 'a'; } <- 在这些实现中是有效的。 - Johannes Schaub - litb
因为C++表示与C相同 - 应用于数组的cv限定符将限定元素而不是数组。但是,它又说,基于其元素类型,可以说数组类型比其他类型“更或少有资格”。我认为这实际上非常棘手。 - Johannes Schaub - litb
我是否忽略了明显的问题,或以下声明/转换并没有完全符合Jed的要求?double (* const a)[n] = (double (* const)[n]) ap;在我的编译器上运行良好(尝试将a=value写入会出错),但是我认为我的编译器可能只是忽略规范。 - Michael Graczyk
2
@MichaelGraczyk дёҚжҳҜзҡ„пјҢйӮЈжҳҜдёҖдёӘжҢҮеҗ‘еҸҜеҸҳж•°з»„зҡ„constжҢҮй’ҲпјҲдҪҝз”ЁжӮЁзҡ„зүҲжң¬е…Ғи®ёa[i][j] = 0.0пјүгҖӮжҲ‘жғіиҰҒдёҖдёӘжҢҮеҗ‘constж•°жҚ®зҡ„жҢҮй’ҲгҖӮиҜ·жіЁж„ҸпјҢgcc -Wcast-qualиӯҰе‘ҠжӮЁзҡ„зүҲжң¬дёўејғдәҶжҢҮй’Ҳзӣ®ж Үзұ»еһӢзҡ„constйҷҗе®ҡз¬ҰгҖӮ - Jed
显示剩余3条评论

1
可能的解决方法适用于C程序员(但不适用于编译器设计师):
使用-Wcast-qual选项的gcc不会对此发出警告:
void g(const double *ap, int n)
{
    int i;
    struct box 
    {
      double a[n];
    };
    const struct box *s = (const struct box *)ap;

    for (i=0; i<n; ++i)
    {
       doStuffWith(s->a[i]);
       /* ... */
    }
}

即使不太优雅,但是在C89和C99之间,尾随数组成员a的含义略有不同,但至少可以获得预期的效果。

实际上它看起来像s[i].a[j],编译器可能会以与a[i][j]相同的方式进行优化。这种解决方案避免了警告,但增加了显著的混淆,特别是对于我使用的四维数组。 - Jed

0

指针(即数组)的情况很尴尬,但这是我对细节的回忆:

const double *ap 是指向常量双精度浮点数的指针;

double *const ap 是指向双精度浮点数的常量指针;

const double *const ap 是指向常量双精度浮点数的常量指针;

所以我相信你所要求的是可能实现的,虽然我已经多年没有尝试过这个了——你正在使用的gcc选项在我上次尝试时还不存在!

编辑:这个答案并不适用于这个问题——我保留它以保留下面的评论,这些评论为普通人(或生疏的C开发人员)澄清了问题。


我的 C 语言有点生疏,但是为什么方法签名中的 const double *const ap 没有完全满足他的要求呢?常量数组引用和常量元素引用? - Ken Gentle
你正在使用的gcc选项在我上次操作时不可用。顺便说一下,-Wcast-qual自2.5.8(1993年)以来就已经存在于gcc中了。 - Jed
@Jed - 这应该能让你想到我上次做这件事的时间!说真的,那可能是在90年代中后期,因此我们当时产品对2.5.8或更早版本的支持非常可能存在。 - Ken Gentle
就C语言而言,指针和数组是不可区分的——增加一个指针,你会得到下一个偏移量,就像a [0]到a [1]一样。原始[]语法是语法糖,更近期的编译器利用上述语法的优势。 - Ken Gentle
@KenGentle:这并不意味着指针就是数组,或者数组类型与指针类型相同。这两种说法都是错误的。 - Lightness Races in Orbit
显示剩余2条评论

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