1[&array]是&array[sizeof(array)/sizeof(array[0])]的适当替代品还是太过晦涩难懂?

6

C语言中没有用于获取数组元素数量的elementsof关键字。 因此,通常会使用sizeof(Array)/sizeof(Array[0])来计算,但需要重复输入数组变量名。 1[&Array]是指向数组之后第一个元素的指针,因此可以这样使用:

int myArray[12] = {1,2,3};  
int *pElement;

for (pElement = myArray; pElement < 1[&myArray]; pElement++)
{
  ...
}

替换:

for (pElement = myArray; pElement < &myArray[sizeof(myArray)/sizeof(myArray[0])]; pElement++)
{
  ...
}

您认为这是否过于混淆不清?

该句子涉及IT技术领域,但未提供足够的上下文。

4
只需定义一个宏_CountElements_,如下所示:#define CountElements(Array) sizeof(Array)/sizeof(Array[0])。然后,您可以简单地使用 int nbofelements = CountElements(myArray); - Jabberwocky
将数组元素类型更改为char,注意到你再也看不到这个错误了。 - Hans Passant
我可以看到这里有很多混淆,你只差一个字节就会出现未定义行为,只是因为C允许访问数组末尾1个指针而得以保存。但是,如果你喜欢"活在边缘",那么这就是最接近的了。 - David C. Rankin
1
@DavidC.Rankin 为什么这个特定的指针超出末尾会比其他任何情况更可怕呢? - Quentin
由于通常使用数组的最后一个元素。C标准确实提供了请求超出数组1的地址是定义良好的,但这可能适用于或不适用于具有动态存储的对象。因此,定制一个可能“有时”有效(只要它是一个数组)的比较方案似乎是一个配方,如果您不小心,在没有定义良好的情况下运用它。请参见C11-§6.5.6加法运算符(p8) - David C. Rankin
显示剩余2条评论
4个回答

5

1[&myArray] 是不明显的。我建议您使用临时变量:

size_t count = sizeof array / sizeof *array;
int * const end = &array[count];
for (pElement = myArray; pElement < end; pElement++)

或者只需使用标准索引变量:

size_t count = sizeof array / sizeof *array;
for(size_t i=0; i<count; ++i) {
    int *pElement = &array[i];

无论你做什么,请使用临时变量,因为你可以给它们取一个描述性的名字。这将使得代码的阅读速度更快,而不会影响运行时性能(除非编译器太蠢了)。

1
int * const end = &myArray + 1;会出现警告 "initialization from incompatible pointer type"。 - notan
那是因为&myarray的类型是type (*)[nelem] - David C. Rankin
要理解 OP 的代码,首先需要知道 a[5] == 5[a],详见此问题。然后你需要知道 &myArray 的类型是 int(*)[12]。因此,1[&myArray] 的类型是 int [12],指向数组末尾刚刚超出的地址。如果你没有理解这个解释,那很好,因为在我看来,这太过混乱了。而这个线程中所有的混乱都证明了这一点。 - user3386109
@user3386109:仍然,1[&myArray]是一个int[12]类型的lvalue,对应于一个想象中的“past-the-end”数组。我想知道让数组到指针的转换形式上是否合法。&1[&myArray]是可以的,因为&*互相抵消。但是1[&myArray]呢? - AnT stands with Russia
@AnT 我现在明白你的观点了。我必须承认我不能够解密标准,以便给出明确的答案。但我猜想这也说明了为什么不应该使用1[&myArray] - user694733
显示剩余6条评论

4
&myarray的类型为int(*)[12],因此当您写1[&myarray]时,相当于写*((&myarray)+1),它指向的是数组myarray最后一个元素的下一个元素。只要我们比较指向相同数组元素或者超过数组最后一个元素的指针(条件是他们是相同类型的),这是合法的。
请注意,我不会说您将int*赋给int(*)[],因为有类型信息的丢失,编译器会报错。 *(&myarray+1) - &myarray的类型为int(*)[12]&myarray+1是指向最后一个元素的下一个元素。对其进行解引用将得到相同的值,但类型不同,即int[12]。在进行比较时,它会衰减为指向第一个元素的指针,这是指向数组最后一个元素的指针int*

同意,尽管通常认为比较是在类似指针之间进行的。(我会让@coderredoc成为使用这种新风格的倡导者 :) - David C. Rankin
不,不,我明白了,这就是为什么我同意你的观点,但你仍然会通常考虑指针之间的比较(即使你可以在类型不可知的地址上进行指针算术运算)。 - David C. Rankin
@coderredoc:我错了。我忽略了[]执行解引用的事实。与&array + 1进行比较是错误的。但是,与1[&array](即*(&array + 1))进行比较是完全可以的。 - AnT stands with Russia
@AnT.:这是我见过的最棘手的问题之一...事实上,我在思考时也感到很奇怪。但我想现在我们明白了。早期的答案是正确的 - 回滚到它。但我想我会再仔细考虑一下。 - user2736738
棘手的部分是将 * 应用于它,然后立即将其衰减为指针。对于不存在的 past-the-end 数组来说,这真的合法吗? - AnT stands with Russia
显示剩余5条评论

1

从正式和严谨的角度来看,1[&array] 方法是无效的。它会导致未定义的行为。

该表达式等同于

(implicit array-to-pointer conversion) *(&array + 1)

这个表达式包含了一个一元运算符 * 应用于指向类型为 int [12] 的“虚构”越界对象的指针。根据6.5.6/8,这个应用被正式地评估并导致未定义行为。

如果结果指向数组对象的最后一个元素之外的位置,则不得将其用作计算的一元*运算符的操作数。

另一方面,这种情况与我们在C89/90中遇到的问题非常相似,当时使用 a + 12 是形成越界指针的有效方式,同时 &a[12] 被认为是未定义的。 &a[12] 等同于 &*(a + 12),也将 * 应用于越界指针,从而导致未定义行为。
C99通过在6.5.3.2/3中规定&*组合应该基本上“消失”来使&a[12]方法合法化,这意味着*不会被计算,也不会触发未定义的行为。
同样地,如果操作数是[]运算符的结果,则既不评估&运算符,也不评估由[]隐含的一元*,结果就好像&运算符被移除,[]运算符被改为+运算符。
我们的情况
(implicit array-to-pointer conversion) *(&array + 1)

本质上相当相似。如果语言标准规定在上下文中数组到指针转换和一元*运算符应该“部分抵消”彼此,只留下简单的隐式(int *)转换,那么这是有道理的。但遗憾的是,语言规范中没有这样的规定。


代码形式上是有效的,行为也完全被定义。一元运算符 * 是对指向数组的指针进行解引用,该指针是指向第一个元素的指针的指针,因此在应用 * 后我们仍然有一个指针。在这种情况下,它是指向 int [12] 数组的第一个元素的指针,紧接着是第一个 int[12] 数组之后。第一个数组结束后的指针永远不会被解引用,因此没有未定义的行为。(尽管如此,该代码并不明显,因此不建议使用) - notan
@notan:“一元是对指向数组的指针进行解引用,该指针是指向第一个元素的指针的指针”-这根本没有任何意义。数组不是指针。指向数组的指针也不是“指向指针”的指针。您描述的其余部分基于这种奇怪的假设。这是不正确的。在这种情况下,一元*的结果是一个数组*(int [12]),而不是指针。 - AnT stands with Russia
是的,到目前为止你是正确的。一元运算符*的结果是(int [12])。标准规定,使用数组会导致指向其第一个元素的指针。 - notan
@notan:当标准说“使用数组会导致指向其第一个元素的指针”时,它指的是众所周知的隐式数组到指针转换,也就是我在回答中提到的。然而,在一元运算符&sizeof等上下文中,这种转换不会发生。这就是为什么&array不是“指向指针”的原因。 - AnT stands with Russia
是的,&array并不完全是指向指针的指针,而是指向一个包含12个int的数组的指针。否则,&array+1将不会指向数组后面的位置。结果的类型是int数组。类型为expression的数组被转换为指向第一个元素的指针。因此,1 [&array]是 &array [sizeof array/sizeof array [0]]。 - notan
@notan:再说一遍:形成一个指向原始myArray之外的虚构“12个int数组”的int (*)[12]指针是完全合法的。但是,该指针只能以非常有限的方式使用。语言规范明确禁止您对这样的指针应用*运算符。就是这样。 - AnT stands with Russia

1
尽管这个建议可能有效,但它远非符合C语言的惯用方式,因此可能会使您的代码难以维护。
个人而言,我会坚持使用传统的ARRAY_SIZE宏:
#define ARRAY_SIZE(a) (sizeof (a) / sizeof (a)[0])

然后将其用作:
for (p = array;  p < array + ARRAY_SIZE(array);  ++p)

你写(a)[0]而不是a[0],或者你是想表达sizeof(a[0])的意思吗?有什么好的理由吗? - machine_1
1
只是标准的宏卫生 - 特别是因为[]的优先级很高。例如,可以编写ARRAY_SIZE(*pa)(如果pa是指向数组的指针),虽然这种方式不太常见。请注意,第一次使用a也是用括号括起来的,原因也差不多。 - Toby Speight
尽管如此,我更喜欢两个宏,第一个类似于您的 #define ARRAY_SIZE(a),然后是一个宏 #define ENDOF(a) (&((a)[ARRAY_SIZE(a)]))#define ENDOF(Array) (1[&(Array)]) 只是一个玩笑,不是生产代码,永远无法通过代码审查。 - notan

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