这个问题标题是否准确描述了编译不通过的C代码?

6

这是一个涉及编程的可能面试问题和答案在这里

以下代码是否会在C语言中编译通过?

#define X 8;
int main(void)
{
    ++X; // will this line compile?
}`

我不是C语言专家,但我了解一些C++,我认为:当然不能对数字8进行递增操作,因为它是一个右值。当然,在尝试编译之前,预处理器会将X替换为8,并且在尝试编译时,由于这个原因,编译会失败。再说,我是那个阅读面试问题网站的人,所以我想谁知道...

这里是给出的解释:

“严格来说,前缀(或后缀)递增运算符的操作数必须是一个不可修改的左值。既然我们知道什么是左值,我们必须问自己X是否是左值。X是一个宏,这意味着它不标识内存中的任何位置-宏通过预处理器使用简单的文本替换。因为宏不存在于内存区域中,所以它们不是左值。这意味着X不能用作前缀递增运算符的操作数。因此,上面显示的代码将无法编译。”

这个解释和我想的一样荒唐吗?

你能发现多少错误?我想也许这应该是面试问题...

这只是有趣的:

"直觉上,你可能会说上面的代码无法编译 - 虽然不知道具体原因。但是,在面试中,你需要提供一些像上面给出的理由。简单的是或否答案在面试中不够用。" (!)


5
不会编译,应该写成#define X 8而不是带分号的形式。 - Andrejs Cainikovs
1
听起来非常...学术... :) - Torp
1
在C89标准下,以及任何合理的警告设置和程序员指南下,不在main函数结尾处使用return 0;或其他数字也是一个错误。 - Chris Lutz
3
它会编译通过,但会产生双分号(即在“++8”语句后的空语句)。 - Chris Lutz
4
我认为这个解释很差。宏可能会扩展成一个左值表达式,所以X 不是指向“内存中的位置”这个事实并不意味着它是一个宏,而是它被定义为什么很重要。例如,#define X var // int main() { int var = 5; ++X; } 是可以的。 - CB Bailey
显示剩余5条评论
5个回答

10

这里给出的解释是不正确的:

X是一个宏,这意味着它不会标识内存中的位置 - 宏使用预处理器进行简单文本替换。

正是因为宏只是简单的文本替换,它们可以扩展为左值,或者其他任何东西。例如:

int x;
#define X x

int main() {
    ++X;
}

这是正确的。虽然宏本身没有在内存中占据位置,但这与++X;是否良构无关,因为++X;并不意味着“增加宏X”,而是表示“展开宏X,然后在前面添加++,在后面添加;,并对结果进行语法和语义分析”。

解释中关于“宏”的内容应该改为关于整数常量8 不是一个左值(lvalue),这里很重要。

在这种情况下,修改后的解释是正确的[编辑 - 正如Chris在评论中指出的那样,仍然不够准确,它写道“前缀(或后缀)递增运算符的操作数必须是不可修改的左值”,应该改为“可修改”]。


1
+1 宏不是 C 语言本身的一部分,与 lvalue、rvalue、表达式和内存完全无关。只有宏的扩展是相关的。(虽然说 ++ 的参数必须是一个不可修改的 lvalue 也是错误的。) - Chris Lutz
1
我不确定说宏不是“C语言本身”的一部分是否有帮助。它们不是可选的,它们是标准的一部分。但是,如果您想将标准划分为“语言”、“库”和“预处理器”,例如sizeof是“语言的一部分”,而INT_MIN不是“语言的一部分”,那么您可以在预处理器、“编译器本身”、标准头文件、链接器和可能的其他组件之间进行清晰的划分,因为翻译阶段在标准中明确分离。 - Steve Jessop
而且,呵呵,我错过了那个打字错误“不可修改”。 - Steve Jessop
我认为除法很有帮助。#define ever (;;) 除非你将其用作 for ever { ... },否则它不会产生任何有意义的语法,但是_只有_宏才能做到这样,因为预处理器在没有任何语法解析的情况下发生。 - Chris Lutz
@Chris:我同意分割是有帮助的,但我谨慎地称这个分割为“C语言”而不是“我将要说的东西虽然是国际标准《编程语言-C》的一部分,但它们并不属于该语言”。我们可以挑剔地说,“啊哈,但是那个标准有一个叫做‘语言’的章节”,但如果我们这样做,我们就会失去有用的区别,因为预处理是该章节的一部分;-)。所以这个分割是有用的,术语略微棘手。对于了解C语言的人来说没有问题,但初学者会因为误导性的名称而受苦。 - Steve Jessop
我并不是指预处理器不属于语言的一部分,只是它不是语言语法的一部分。我可以说“语法”,但语法只是整个语言的一小部分。memcpy(0, 0, 5);不是一个“语法”错误。 - Chris Lutz

5

这个解释像我想的那样站不住脚吗?

是的。

“严格来说,前缀(或后缀)递增运算符的操作数必须是一个非可修改的左值...

什么?非可修改的左值就像 const int n; - 你可以通过 & 取其地址,但你不能通过 =+=++ 来赋值。你不能递增不可修改的东西。

引用标准(6.5.3.1 第一段):

前缀递增或递减运算符的操作数必须具有限定或未限定的实数或指针类型,并且必须是可修改的左值。

啊嗯。

X 是一个宏,这意味着它不标识内存中的位置 - 宏使用预处理器进行简单的文本替换。

这是错误的。宏在C语言中不存在,它们是预处理器的一部分,预处理器没有关于lvalue、rvalue、表达式或内存的概念。这个特定的宏展开为一个整数常量,它是一个rvalue,但宏本身与上述任何内容都无关。请参见Steve Jessop's answer,了解宏作为lvalue的反例。
正确答案是该语句扩展为++8,由于8是一个rvalue,因此无法将其用作++(以任何形式)的参数,所以它不会编译。此外,根据您是否打算将此代码编译为C89或C99,使main没有明确的return值可能会导致未定义的行为。

* 如果这将成为被接受的答案,我想我应该澄清一下:预处理器是C编程语言的一部分。它在C标准中指定,编译器必须实现预处理才能成为C编译器。然而,C“语言”(即语法、语义、库等)不与预处理器交互——一旦你开始处理lvalue和rvalue,预处理器早已完成所有宏的完全展开。宏本身在语法中没有任何位置,因为它们不是“语言”的一部分。在Steve Jessop的回答中有一些关于是否在这里使用术语“语言”会误导的争议,我同意他的看法,只是找不到更好的词来代替。


2
它无法编译的原因和以下情况完全相同:
8 = 8+1;

无法编译。

您无法修改(在此处递增)常量。


2

这个解释只提到了代码无法编译,但在其他方面完全是错误的。

它完全忽略了编译器不会看到X,而是会看到++8;;,因此关于运算符是否能应用于X的所有讨论都没有意义。回答“不”要比给出这样的解释好得多。


0

它将无法编译,因为前缀运算符需要位置值。由于8是一个常量,所以会失败。您可以在这里进一步了解L-value:L-Value and R-Value Expressions


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