【问题标题】:Const correctness for array pointers?数组指针的常量正确性?
【发布时间】:2016-03-24 11:49:16
【问题描述】:

有人提出,在现代 C 语言中,我们应该始终通过数组指针将数组传递给函数,因为数组指针具有强类型。示例:

void func (size_t n, int (*arr)[n]);
...

int array [3];
func(3, &array);

这听起来可能是防止各种类型相关和数组越界错误的好主意。但后来我突然想到我不知道如何将 const 正确性应用于此。

如果我这样做void func (size_t n, const int (*arr)[n]),那么它是正确的。但是由于指针类型不兼容,我无法再传递数组。 int (*)[3]const int (*)[3]。限定符属于指向的数据,而不属于指针本身。

调用者中的显式转换会破坏增加类型安全的整个想法。

如何将 const 正确性应用于作为参数传递的数组指针?有可能吗?


编辑

就像信息一样,有人说像这样通过指针传递数组的想法可能起源于 MISRA C++:2008 5-2-12。参见例如PRQA's high integrity C++ standard

【问题讨论】:

  • 我想关键是在设计一切时都要考虑到 const 正确性?
  • 正确的传递指针比你传递的数组少一维。 int *arrint ia[3]。这样你只需传递数组本身。或者,对称 - 只需对传递的数组使用相同的参数声明:int arr[] 并使用形式参数的隐式转换。
  • @Olaf 不,那么你会得到数组衰减并且整个增加的类型安全参数都丢失了。与 int* param 相比,参数 int param[n] 没有增加类型安全性。
  • @Lundin:好的,所以您接受为每个阵列访问添加的*。明白了。
  • 这就是为什么我个人认为 MISRA 是一本不错的读物,但主要是因为理性。将风格指南视为一种宗教总是不好的。如果涉及合规性与质量,我总是更喜欢后者。 (我很清楚我们并不总是有选择的余地)

标签: c const-correctness


【解决方案1】:

除了演员表之外没有其他办法。这是以这种方式传递数组的想法的重大缺点。

Here is 一个类似的线程,将 C 规则与 C++ 规则进行比较。我们可以从这个比较中得出结论,C 规则设计得不是很好,因为您的用例是有效的,但 C 不允许隐式转换。另一个这样的例子是将T ** 转换为T const * const *;这是安全的,但 C 不允许。

请注意,由于n 不是常量表达式,因此int n, int (*arr)[n]int n, int *arr 相比没有任何额外的类型安全性。你仍然知道长度 (n),并且它仍然是无声的未定义行为来越界访问,并且仍然是无声的未定义行为来传递一个实际上不是长度的数组n

这种技术在传递非 VLA 数组的情况下更有价值,当你传递一个指向错误长度数组的指针时,编译器必须报告。

【讨论】:

  • VLA 部分并不是真正需要的,这只是我的示例。普通静态大小的数组也存在同样的问题。
  • @Lundin 是的。有一次我使用了一个设置 typedef char KEY_BUFFER[8]; typedef char const CKEY_BUFFER[8]; ,如果调用一个接受 const 缓冲区的函数,请编写如下代码:KEY_BUFFER buf; ... check_buf( (CKEY_BUFFER *)&buf ); ,但是看看最终结果,你必须使用强制转换意味着几乎没有收获在类型安全和可读性方面的成本。我最终只是不使用 const 版本并依赖文档。如果从头开始编写,我可能不会再次尝试该设置。
  • @M.M 我想到了使用宏#define const_cast(n, arr) _Generic(arr, int(*)[n] : (const int(*)[n])arr )。然后强制调用者使用该宏,与您的示例的想法相同,但据我所知,这应该是类型安全的。尽管如此,它还是给调用者代码增加了混乱。
  • 鉴于参数声明int n, int (*arr)[n],传递小于n 的数组是不是未定义的行为 - 您需要使用int n, int (*arr)[static n]。如果没有staticn 将被完全忽略,并且传递一个较小的数组是明确定义的(not great IMO 但我们有)。当然 MISRA 禁止使用static(因为 MISRA 作者不了解它的用途,并且更喜欢不太安全的代码,R17.6)。
  • @Leushenko C11 6.7.6.2/6 表示如果对应于int (*arr)[n] 的参数不是int[n] 类型数组的地址,则它是未定义的行为。在int (*arr)[static n] 中,static 无效。您将这种情况与int v[n]int v[static n] 的参数声明混为一谈(您的分析是正确的)。
【解决方案2】:

C 标准说(部分:§6.7.3/9):

如果数组类型的规范包括任何类型限定符,则元素类型是这样限定的,不是数组类型。[...]

因此,在const int (*arr)[n] 的情况下,const 应用于数组的元素,而不是数组arr 本身。 arr 的类型是 pointer to array[n] of const int,而您正在传递 pointer to array[n] of int 类型的参数。两种类型都不兼容。

如何将 const 正确性应用于作为参数传递的数组指针?有可能吗?

这是不可能的。如果不使用显式强制转换,在标准 C 中就无法做到这一点。

但是,GCC 允许将其作为extension

在 GNU C 中,指向带有限定符的数组的指针与指向其他限定类型的指针类似。例如,int (*)[5] 类型的值可用于初始化const int (*)[5] 类型的变量。 这些类型在 ISO C 中是不兼容的,因为 const 限定符正式附加到数组的元素类型而不是数组本身

 extern void
 transpose (int N, int M, double out[M][N], const double in[N][M]);
 double x[3][2];
 double y[2][3];
 ...
 transpose(3, 2, y, x); 

延伸阅读:Pointer to array with const qualifier in C & C++

【讨论】:

  • 嗯,是的,这就是我在问题中解释的内容? “但由于指针类型不兼容,我无法再传递数组。”这是问题,而不是解决方案。还是说没有解决办法?我只对标准 C 感兴趣。
  • @Lundin;很抱歉,目前还没有解决方案。但是,您可以使用 GCC 扩展。
  • gcc 与 C - 1:0 。 @Lundin:谈到 const 正确性,C 有很多陷阱。 IIRC 有一个关于混合 const/not const 的嵌套指针的问题,无法正确检测到。你的主要是因为语法错误,因此 gcc 扩展很可能不会成为标准。您实际上必须将const 应用于int [3](这将是某些派生语言使用它的原因之一)。
【解决方案3】:

OP 描述了一个具有以下签名的函数func()

void func(size_t n, const int (*arr)[n])

OP 想调用它传递各种数组

#define SZ(a) (sizeof(a)/sizeof(a[0]))

int array1[3];
func(SZ(array1), &array1);  // problem

const int array2[3] = {1, 2, 3};
func(SZ(array2), &array2);

如何将 const 正确性应用于作为参数传递的数组指针?

对于 C11,使用 _Generic 根据需要进行强制转换。 only 在输入是可接受的非const 类型时发生,从而保持类型安全。这是“如何”做到这一点。 OP 可能会认为它“臃肿”,因为它类似于this。这种方法将宏/函数调用简化为仅 1 个参数。

void func(size_t n, const int (*arr)[n]) {
  printf("sz:%zu (*arr)[0]:%d\n", n, (*arr)[0]);
}

#define funcCC(x) func(sizeof(*x)/sizeof((*x)[0]), \
  _Generic(x, \
  const int(*)[sizeof(*x)/sizeof((*x)[0])] : x, \
        int(*)[sizeof(*x)/sizeof((*x)[0])] : (const int(*)[sizeof(*x)/sizeof((*x)[0])])x \
  ))

int main(void) {
  #define SZ(a) (sizeof(a)/sizeof(a[0]))

  int array1[3];
  array1[0] = 42;
  // func(SZ(array1), &array1);

  const int array2[4] = {1, 2, 3, 4};
  func(SZ(array2), &array2);

  // Notice only 1 parameter to the macro/function call
  funcCC(&array1);  
  funcCC(&array2);

  return 0;
}

输出

sz:4 (*arr)[0]:1
sz:3 (*arr)[0]:42
sz:4 (*arr)[0]:1

代码也可以使用

#define funcCC2(x) func(sizeof(x)/sizeof((x)[0]), \
    _Generic(&x, \
    const int(*)[sizeof(x)/sizeof((x)[0])] : &x, \
          int(*)[sizeof(x)/sizeof((x)[0])] : (const int(*)[sizeof(x)/sizeof((x)[0])])&x \
    ))

funcCC2(array1);
funcCC2(array2);

【讨论】:

  • 这与我想出的技巧大致相同,请参阅 cmets。不确定使用这样的宏是否是个好主意……你增加了很多复杂性,可能弊大于利。无论如何,您都必须记录宏并说“为了调用 func,请调用 funcCC”。因此,您不妨记录下缺少 const 正确性的原始函数并说“嘿,我保证我不会触摸传递的内容”。
  • @Lundin 在我完成的许多编程任务中,我不会将其评为“非常复杂”。为了最好地评价(过度)复杂性,最好看到更多的整体情况和竞争选择。这确实维护了类型安全,这是一个既定目标。复杂性不是一个公开的目标,但这确实简化了函数的调用。肯定比func(3, &array); 好。无论如何 - 有趣的问题 - 探索 C 的新角落。
  • @Lundin BTW:这样一个高度评价和有趣的问题在接受答案之前应该有更多的时间。我相信,如果长时间不加重音,其他想法答案更有可能出现。
猜你喜欢
  • 2012-02-07
  • 1970-01-01
  • 2011-06-30
  • 1970-01-01
  • 2014-01-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多