gcc 为什么连这种代码都能编译通过?

2022-06-18 11:14:39 +08:00
 kgdb00
#include <stdio.h>

int main(int argc, char *argv[])
{
	char *s = argv[argc-1];
	printf("%s\n", s);

	char *s2 = argc[argv-1];
	printf("%s\n", s2);

	return 0;
}

打印最后一个参数,上面的写法是正确的,问题是下面的写法也能编译通过,而且打印输出和上面的写法一样,不明白为什么编译器允许 argc[argv-1] 这种写法。

7119 次点击
所在节点    Linux
42 条回复
mingl0280
2022-06-18 23:45:46 +08:00
因为这就是标准定义:
When an expression that has integer type is added to or subtracted from a pointer, the result has the type of the pointer operand. If the pointer operand points to an element of an array object, and the array is large enough, the result points to an element offset from the original element such that the difference of the subscripts of the resulting and original array elements equals the integer expression. In other words, if the expression P points to the i-th element of an array object, the expressions (P)+N (equivalently, N+(P)) and (P)-N (where N has the value n) point to, respectively, the i+n-th and i−n-th elements of the array object, provided they exist. Moreover, if the expression P points to the last element of an array object, the expression (P)+1 points one past the last element of the array object, and if the expression Q points one past the last element of an array object, the expression (Q)-1 points to the last element of the array object. If both the pointer operand and the result point to elements of the same array object, or one past the last element of the array object, the evaluation shall not produce an overflow; otherwise, the behavior is undefined. If the result points one past the last element of the array object, it shall not be used as the operand of a unary * operator that is evaluated.
注意 18 楼的理解是错误的。
mingl0280
2022-06-18 23:46:31 +08:00
@kgdb00 C 标准就这么规定的,不是什么“不符合 C 语言的语法规范”
xfriday
2022-06-19 01:08:17 +08:00
@xiri 虽然我知道为什么它们相等,也知道符合规范,但是不妨碍我认为这规范就是坨 shi ,这坨 shi 除了玩花样以外没有带来任何用处,a[b]=*(a+b)=*(b+a)=b[a] 这个等式只在 a 和 b 当中有 1 个是可以用下标访问的,1 个是整数才成立; 2 个都是可以用下标访问的或都是整数就过不了,这规范跟个八股文似的
xfriday
2022-06-19 01:10:51 +08:00
这坨 shi 就像说 a ÷ b = b ÷ a 一样
geelaw
2022-06-19 01:55:14 +08:00
@mingl0280 #20 #21 我帮你节选了你需要关注的部分:

If both the pointer operand and the result point to elements of the same array object, or one past the last element of the array object, the evaluation shall not produce an overflow; otherwise, the behavior is undefined.

现在你能理解 #18 的意思了吗?
MrKrabs
2022-06-19 02:20:57 +08:00
意义不明反直觉
mingl0280
2022-06-19 05:32:51 +08:00
@geelaw 我节选的段落里面已经非常清楚地表述了求值顺序:先使用 N+(P)(同样地,N-(P)或(P)-N )求得 i+(n-th)或 i-(n-th)个元素,然后判断操作数和结果指针是否越界,如果其中**一个**没有越界,则结果**不**应该溢出;否则,结果未定义。
根据这个手册的内容,首先,P 是 argv ,N 是-1 ,先求得 argv-1 的指针,该指针没有越过 argv 的最后一个项,因此值合法且指向数组最后一项;然后,argc[...]等价于(...)[argc],该顺序下不存在未定义行为,不存在代码错误。
现在你告诉我,18 楼理解哪里正确了?
你截取的部分只能证明你连这段话都没看懂。
mingl0280
2022-06-19 05:42:01 +08:00
@xfriday 这其实是标准那边应该是想把 a[N] 同等于 N[a]搞出来的交换律,数学上加法和乘法符合交换律,除法和减法不符合交换律,一样的道理。
AX5N
2022-06-19 07:25:40 +08:00
@xfriday 与其说是“玩花样”,倒不如说是“不玩花样”才导致了这种灵活性。
geelaw
2022-06-19 11:09:35 +08:00
@mingl0280 #27 您的翻译是错误的,但您的错误不止于此。

>如果其中**一个**没有越界,则结果**不**应该溢出

If BOTH the pointer operand AND the result point to elements of the same array object, or one past the last element of the array object, the evaluation shall not produce an overflow; otherwise, the behavior is undefined.

这句话的意思是:如果指针运算数和结果都指向同一个数组对象里的元素或该数组对象最后一个元素之后的位置,求它的值无溢出;否则,行为无定义。

假设 (P)+N 或 N+(P) 或 (P)-N 里的 P 指向数组里的下标是 i 的元素(如果是最后一个元素之后的位置,则令 i=M 为数组里的长度),令 i+N 或 i-N 为 j (取决于运算),这个定义要求 0 <= i <= M 且 0 <= j <= M ,否则行为未定义。

以上是英语和汉语的问题。下面是数学问题

> P 是 argv ,N 是-1 ,先求得 argv-1 的指针,该指针没有越过 argv 的最后一个项,因此值合法且指向数组最后一项

#18 已经说明 argv 可以指向某数组的第一个元素(即下标是 0 的元素),此时 argv-1 是“第一个元素之前的位置”(这个概念只存在于你我的想象中,不存在于 C 语言里),您怎么会认为 argv-1 是该数组的最后一个元素呢?
Kasumi20
2022-06-19 11:14:37 +08:00
a[b] 应该等价于 *(a + b*sizeof(*a))。
mingl0280
2022-06-19 12:11:40 +08:00
@geelaw 我回去重新想了下这里问题出在哪。我之前那个说法应该是错误的。
现在我还有一个比较坑爹的说法。
argc[argv-1] = *((argc) + *((argv) - (1)))
此处 argv 地址为 0x00000000
那么第一次运算的 ptr 指向 0x0-4 = FFFC ,此时 ptr 能否解引用?应该是可以的吧。
第二次运算的 ptr 为 FFFC + argc ,又回来了。(因为 argc 至少为 1 )
*((argc) + argv) 的地址就不会越过 0 ,这个结果就是合法的。
这是另一个想法。
geelaw
2022-06-19 12:55:52 +08:00
@mingl0280 #32 我觉得您开始混淆应然和实然的问题了,从 #18 以来的问题并不考虑 argc[argv-1] 在最近常见的电脑的常见操作系统的常见编译器上是否对应 argv[argc-1]。

> argc[argv-1] = *((argc) + *((argv) - (1)))

这个想法也是错误的,argc[argv-1] 等价于 *(argc + (argv - 1)),改成 *(argc + *(argv - 1)) 是完全不同的意思,后者等价于 argc[argv[-1]],很明显也是不可移植代码。

认为指针的运算等于“地址数值”的运算也是错误的,更不能认为 p - 1 + 1 等同于 p ,最简单的例子:

int a[1] = {};
int *p = a - 1 + 1;

这段代码里 p 的初始化表达式蕴含着未定义行为,因为 a - 1 是不存在的概念。改成下面这样就没问题了:

int a[1] = {};
int *p1 = a + 1 - 1;
int *p2 = a - (-1) + (-1);
int *p3 = (int *)((uintptr_t)a - sizeof(int) + sizeof(int));

前两个版本的运算从来没有离开 a 的元素或元素之后的位置,第三个版本里,无符号数的加减运算无溢出,并且 uintptr_t 和指针之间的转换保证数值上的返程关系。
2NUT
2022-06-19 14:07:33 +08:00
[] 下标标记 只是 指针加 的语法糖
phithon
2022-06-19 14:46:45 +08:00
众所周知,C 是弱类型语言
zxCoder
2022-06-19 17:22:12 +08:00
羡慕 C 程序员的大脑
LANB0
2022-06-20 10:42:40 +08:00
指针啊,C 语言里一切变量函数皆指针,给足了程序员发挥空间。而不是像很多高级语言,给足了语法糖大礼包,程序员只是个两点一线的工蜂。
FrankHB
2022-06-24 21:40:57 +08:00
@LANB0 你在说啥?函数是什么鬼指针,你 sizeof 试试?
又有几个语言有比 C 的得 [] 更加标准的源语言层面的纯语法糖而不是混了坨 unspecified 没法一一映射实现的?
FrankHB
2022-06-24 21:48:29 +08:00
好像没人直接回答标题的字面问题。

WG14 N2176
5.1.1.3 Diagnostics
1 A conforming implementation shall produce at least one diagnostic message (identified in an implementation-defined manner) if a preprocessing translation unit or translation unit contains a violation of any syntax rule or constraint, even if the behavior is also explicitly specified as undefined or implementation-defined. Diagnostic messages need not be produced in other circumstances. 9
9) The intent is that an implementation should identify the nature of, and where possible localize, each violation. Of course, an implementation is free to produce any number of diagnostics as long as a valid program is still correctly translated. It may also successfully translate an invalid program.

注意最后一句话。
LANB0
2022-06-27 10:24:05 +08:00
@FrankHB sizeof 可以用于函数名称?不如打印函数名称和函数名称取地址的 16 进制看看是不是一个东西?[]作为指针的另一种写法也好意思叫语法糖了?作为一根指针行天下的 C 程序员,题主的问题还需要去翻一堆标准文档?和下面这个宏有什么区别?
define offsetof(TYPE, MEMBER) ((int) &((TYPE *)0)->MEMBER)

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/860466

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX