问题 错误既不是语法也不是语义?


我在家庭作业上有这个问题(不用担心,已经完成):

[使用您最喜欢的命令式语言,举个例子   每个......]编译器既不能捕获也不能轻易生成代码的错误   catch(这应该违反语言定义,而不仅仅是a   程序错误)

来自“编程语言语用学”(第3版)Michael L. Scott

我的回答,打电话 main 从 main 通过传入相同的参数(在C和Java中),受到启发 这个。但我个人觉得这只是一个语义错误。

对我来说,这个问题是在询问如何产生既不是语法也不是语义的错误,坦率地说,我无法想到它也不会陷入其中的情况。

是否容易被利用的代码,如缓冲区溢出(以及我从未听说过的其他利用)?某种坑从语言结构中掉落(IDK,但懒惰的评估/弱类型检查)?我想在Java / C ++ / C中使用一个简单的例子,但欢迎其他例子。


7131
2018-01-24 01:09


起源

僵局和种族 - John Dibling
在Java中,几乎所有由异常发出的故障。 - Raedwald
这个问题似乎是偏离主题的,因为它属于 programmers.stackexchange.com - DanMan


答案:


未定义的行为 想起来。调用UB的语句在语法和语义上都不正确,但是代码的结果无法预测并被认为是错误的。

这样做的一个例子是(来自维基百科页面)尝试修改字符串常量:

char * str = "Hello world!";
str[0] = 'h'; // undefined-behaviour here

但并非所有UB语句都如此容易识别。例如,如果用户输入的数字太大,请考虑在这种情况下签名整数溢出的可能性:

// get number from user
char input[100];
fgets(input, sizeof input, stdin);
int number = strtol(input, NULL, 10);
// print its square: possible integer-overflow if number * number > INT_MAX
printf("%i^2 = %i\n", number, number * number);

这里可能不一定有符号整数溢出。由于它涉及用户输入,因此无法在编译或链接时检测到它。


7
2018-01-24 01:17



char * str = "Hello world!"; 不应该用C ++编译(不确定C),因为字符串文字的类型是 const char * (实际上它通过数组到指针转换衰减到了......) - MFH
@MFH在C中,字符串文字没有 const 类型。 (但你仍然不允许实际修改它们。) - This isn't my real name
@MFH:在C ++中,字符串文字的类型是“数组的 const char“,但在C中它是”阵列的 char“。所以在C ++ 03中有一个从字符串文字隐式转换为 char*,为了与C兼容。这种兼容性在C ++ 03中已弃用,在C ++ 11中已删除。因此,如果使用C ++ 11,“不应该在C ++中编译”,大多数人都不是。或者如果使用例如 -Wwrite-strings -Werror,大多数人不是,但也许应该;-) - Steve Jessop


声明调用 未定义的行为1 在语义上和语法上都是正确的但是使程序行为不规律。

a[i++] = i;   // Syntax (symbolic representation) and semantic (meaning) both are correct. But invokes UB.   

另一个例子是使用指针而不初始化它。
逻辑错误 既不是语义也不是句法。


1。 未定义的行为:任何事情都可能发生;标准没有要求。程序可能无法编译,或者可能无法正确执行(崩溃或静默生成不正确的结果),或者它偶然可能完全符合程序员的意图。 


5
2018-01-24 01:21



我看到这个也在@Kninnug提供的维基百科页面中,我没有想到有UB的那个简单的声明。 - SGM1
@ SGM1更简单的一个 i = i++; :) - Kninnug
@Kninnug;真的:) - haccks
编译器 能够 抓住这个例子,通常他们不会打扰。所以对这个问题有一些解释,是否需要一个编译器的例子 一定 无法捕获,或只是你的编译器无法捕获的一个,因为它不够聪明...... - Steve Jessop
@SteveJessop这也是由于未定义行为的定义: “使用不可移植或错误的程序结构或错误数据的行为,本国际标准 没有要求“。编译器不需要引发错误甚至可能 让恶魔从你的鼻子里出来。 - Kninnug


这是C ++的一个例子。假设我们有一个功能:

int incsum(int &a, int &b) {
    return ++a + ++b;
}

然后,下面的代码具有未定义的行为,因为它修改了一个对象两次,没有插入序列点:

int i = 0;
incsum(i, i);

如果打电话给 incsum 与函数的定义处于不同的TU,然后就是 不可能 在编译时捕获错误,因为代码本身都没有本身错误。它可以在链接时通过足够智能的链接器检测到。

您可以根据需要生成任意数量的示例,其中一个TU中的代码具有对于由另一个TU传递的某些输入值有条件地未定义的行为。我选择了一个略显模糊的,你可以轻松地使用无效指针解除引用或有符号整数算术溢出。

你可以说生成代码来捕获它是多么容易 - 我不会说它很容易,但编译器可能会注意到 ++a + ++b 如果是无效的 a 和 b 别名相同的对象,并添加相当于 assert (&a != &b); 在那条线上。因此,可以通过本地分析生成检测代码。


2
2018-01-24 01:42



这不是C,这是C ++。 C没有参考。 - This isn't my real name
@ElchononEdelson:谢谢,修好。我不知道为什么我写“C”:-) - Steve Jessop