我在自学CSAPP,在运行断言测试时遇到了一个奇怪的问题,得到了一个奇怪的结果。
我不确定该从什么开始这个问题,所以让我先获得代码(文件名可见于注释中):
// File: 2.30.c
// Author: iBug
int tadd_ok(int x, int y) {
if ((x ^ y) >> 31)
return 1; // A positive number and a negative integer always add without problem
if (x < 0)
return (x + y) < y;
if (x > 0)
return (x + y) > y;
// x == 0
return 1;
}
// File: 2.30-test.c
// Author: iBug
#include <assert.h>
int tadd_ok(int x, int y);
int main() {
assert(sizeof(int) == 4);
assert(tadd_ok(0x7FFFFFFF, 0x80000000) == 1);
assert(tadd_ok(0x7FFFFFFF, 0x7FFFFFFF) == 0);
assert(tadd_ok(0x80000000, 0x80000000) == 0);
return 0;
}
和命令:
gcc -o test -O0 -g3 -Wall -std=c11 2.30.c 2.30-test.c
./test
2.30-test.c:13: main: assertion "tadd_ok(0x7FFFFFFF, 0x7FFFFFFF) == 0" failed
因此,我在手机上启动了gdb
,试图获得一些见解:
(gdb) l 2.30.c:1
1 // File: 2.30.c
2 // Author: iBug
3
4 int tadd_ok(int x, int y) {
5 if ((x ^ y) >> 31)
6 return 1; // A positive number and a negative integer always add without problem
7 if (x < 0)
8 return (x + y) < y;
9 if (x > 0)
10 return (x + y) > y;
(gdb) b 2.30.c:10
Breakpoint 1 at 0x728: file 2.30.c, line 10.
(gdb) r
Starting program: /data/data/com.termux/files/home/CSAPP-2019/ch2/test
warning: Unable to determine the number of hardware watchpoints available.
warning: Unable to determine the number of hardware breakpoints available.
Breakpoint 1, tadd_ok (x=2147483647, y=2147483647)
at 2.30.c:10
10 return (x + y) > y;
(gdb) p x
$1 = 2147483647
(gdb) p y
$2 = 2147483647
(gdb) p (x + y) > y
$3 = 0
(gdb) c
Continuing.
2.30-test.c:13: main: assertion "tadd_ok(0x7FFFFFFF, 0x7FFFFFFF) == 0" failed
Program received signal SIGABRT, Aborted.
0x0000007fb7ca5928 in abort ()
from /system/lib64/libc.so
(gdb) d 1
(gdb) p tadd_ok(0x7FFFFFFF, 0x7FFFFFFF)
$4 = 1
(gdb)
正如您在GDB输出中看到的,结果非常不一致,因为到达了2.30.C:10
上的return
语句,返回值应该是0,但是函数仍然返回1,使得断言失败。
请提供一个想法,我在这里做错了什么。
请尊重我所提出的。只说它是UB,而不联系平台,尤其是GDB输出,是没有任何帮助的。
签名溢出是ISO C中未定义的行为。您不能可靠地导致它,然后检查它是否发生。
在表达式(x+y)>y;
中,编译器可以假设x+y
没有溢出(因为这将是UB)。因此,它优化到检查x>0
。(是的,真的,gcc甚至在-o0
时也这样做)。
这种优化在GCC8中是新的。在x86和aarch64上是一样的;您一定在AArch64和x86上使用了不同的GCC版本。(即使在-O3
,gcc7.x和更早版本(故意?)也没有进行这种优化。clang7.0也没有这样做。它们实际上执行32位的添加和比较。它们也没有优化TADD_OK
以return1
,或者优化add
并检查溢出标志(v
,在ARM上v
,在x86上of
)。clang优化的asm是>31
,以及一个XOR操作的有趣混合,但是
您可以说gcc8“破坏”了您的代码,但实际上,就合法/可移植的ISO C而言,它已经被破坏了。gcc8只是揭示了这一事实。
为了更清楚地看到它,让我们将这个表达式隔离到一个函数中。gcc-o0
无论如何都会单独编译每个语句,因此只有在x<0
时才运行的信息不会影响tadd_ok
函数中此语句的-o0
code-gen。
// compiles to add and checking the carry flag, or equivalent
int unsigned_overflow_test(unsigned x, unsigned y) {
return (x+y) >= y; // unsigned overflow is well-defined as wrapping.
}
// doesn't work because of UB.
int signed_overflow_expression(int x, int y) {
return (x+y) > y;
}
在带有AArch64 gcc8.2-o0-fverbose-asm
的Godbolt编译器资源管理器上:
signed_overflow_expression:
sub sp, sp, #16 //,, // make a stack fram
str w0, [sp, 12] // x, x // spill the args
str w1, [sp, 8] // y, y
// end of prologue
// instructions that implement return (x+y) > y; as return x > 0
ldr w0, [sp, 12] // tmp94, x
cmp w0, 0 // tmp94,
cset w0, gt // tmp95, // w0 = (x>0) ? 1 : 0
and w0, w0, 255 // _1, tmp93 // redundant
// epilogue
add sp, sp, 16 //,,
ret
;; Function signed_overflow_expression (null)
;; enabled by -tree-original
{
return x > 0;
}
不幸的是,即使使用-wall-wextra-wpedantic
,也没有关于比较的警告。这不是微不足道的事实;它仍然取决于x
。
优化后的asm是cmp w0,0
/cset w0,gt
/ret
。与0xff
是冗余的。CSET
是CSINC
的别名,使用零寄存器作为两个源。所以它会产生0/1。对于其他寄存器,CSINC
的一般情况是任意2个寄存器的条件选择和增量。
无论如何,cset
是AARCH64与x86setcc
的等价物,用于将标志条件转换为寄存器中的bool
。
因此,您可以说在这场战争中“开火了”,因为GCC8.x中的更改实际上是“破坏”不安全的代码,就像这样。:P
请参见在C/C++中检测有符号溢出以及如何在没有未定义行为的情况下检查C中有符号整数溢出?
由于有符号加法和无符号加法在2的补码中是相同的二进制运算,所以对于加法,您可以直接强制转换为unsigned
,而对于有符号比较,则可以强制转换回来。这将使您的函数版本在“普通”实现中是安全的:2的补码,而在无符号
和int
之间的转换只是对相同位的重新解释。
return (int)((unsigned)x + (unsigned)y) > y;
这将编译(对于AArch64,gcc8.2-o3)到
add w0, w0, w1 // x+y
cmp w0, w1 // x+y cmp y
cset w0, gt
ret
如果将int sum=x+y
作为与return sum
gcc
具有默认的
-o0
也可以看到它。
编译时可见的UB是各种不好的。在这种情况下,只有特定范围的输入才会产生UB,所以编译器假定它不会发生。如果在执行路径上看到无条件UB,优化编译器可以假设路径从未发生。(在没有分支的函数中,它可以假设该函数从未被调用,并将其编译为一条非法指令。)参见C++标准允许未初始化的bool使程序崩溃吗?有关编译时可见UB的更多信息。
问题内容: 我正在尝试执行此命令 和 都不起作用(返回空白输出) 有人知道为什么吗? 问题答案: 因为top是一个交互式程序,旨在在终端上运行,而不是从脚本中执行。您可能需要运行带有参数的“ ps”命令,这些命令将按cpu利用率对输出进行排序。 http://www.devdaily.com/linux/unix-linux-process-memory-sort-ps-command- cpu
在上调用时: gcc和clang都在std::数组的排序上返回一个错误——clang说 错误:使用未声明的标识符“sort”;你是说“性病::分类”吗? 更改为解决了这个问题。 MSVC按编写的方式编译上面的代码。 为什么和在治疗上存在差异;哪个编译器是正确的?
我正在尝试制作两个程序。我希望一个打印我居住的城市的当前天气,我希望另一个从一个在线帐户获取数据并返回它。对于这些脚本,我导入了天气模块和请求模块。当我在shell中导入它们时,没有问题,但是当我运行脚本时,它说“ImportError:No module name you weather”。我做错了什么? 壳牌: 脚本: 这也适用于“天气”模块 谢谢你
我正在尝试执行GET命令,以便我可以从服务器获取数据。下面的Curl适用于Postman。 在运行我的代码时,我能够获取会话ID。下一步是获取数据。但是当我执行GET时,我没有得到任何响应。相反,我得到一个错误,如下所示:“指定的值具有无效的HTTP标头字符。(参数'name')” 下面是我试图执行的C代码 问题:我没有收到来自服务器的响应,响应长度为零。 以下是答案:0 回答ErrorMessa
我已经编写并成功安装了一个苹果tonto JAVACOS智能卡。 我可以将这个小程序安装到我的NXP J3H145和ACOSJ卡上,但是当我尝试与它交互时,我得到了错误代码6F00。 这是为什么? 我正在使用JCIDE开发小程序,并使用Global Platform Pro安装小程序。
我正在为一个tomcat实例修补RH Linux7上的自签名证书,但有一段时间,我没有浏览器警告。我在这里遵循了这些人的说明(让Chrome接受自签名的本地主机证书),并尝试使用KeyTool将.crt导入到我的tomcat实例中。使用以下命令- 创建密钥库-keytool-keysize 2048-genkey-alias tomcat-keyalg RSA-keystore tomcat.ke