看完后隐藏功能和C++/STL的暗角上comp.lang.c++.moderated,我完全惊讶的是,下面的代码片断编译并在两个Visual Studio 2008和G ++ 4.4的工作.
这是代码:
#include <stdio.h>
int main()
{
int x = 10;
while (x --> 0) // x goes to 0
{
printf("%d ", x);
}
}
Run Code Online (Sandbox Code Playgroud)
我假设这是C,因为它也适用于GCC.标准中定义了哪里,它来自何处?
编译:
#include <iostream>
int main()
{
for (int i = 0; i < 4; ++i)
std::cout << i*1000000000 << std::endl;
}
Run Code Online (Sandbox Code Playgroud)
并gcc产生以下警告:
warning: iteration 3u invokes undefined behavior [-Waggressive-loop-optimizations]
std::cout << i*1000000000 << std::endl;
^
Run Code Online (Sandbox Code Playgroud)
我知道有一个有符号的整数溢出.
我不能得到的是为什么i价值被溢出操作打破了?
我已经阅读了为什么x86上的整数溢出与GCC导致无限循环的答案?,但我仍然不清楚为什么会发生这种情况 - 我认为"未定义"意味着"任何事情都可能发生",但这种特定行为的根本原因是什么?
编译: gcc (4.8)
我遇到了下面的C++程序(源代码):
#include <iostream>
int main()
{
for (int i = 0; i < 300; i++)
std::cout << i << " " << i * 12345678 << std::endl;
}
Run Code Online (Sandbox Code Playgroud)
它看起来像一个简单的程序,并在我的本地机器上提供正确的输出,例如:
0 0
1 12345678
2 24691356
...
297 -628300930
298 -615955252
299 -603609574
Run Code Online (Sandbox Code Playgroud)
但是,在像codechef这样的在线IDE上,它提供了以下输出:
0 0
1 12345678
2 24691356
...
4167 -95167326
4168 -82821648
4169 -7047597
Run Code Online (Sandbox Code Playgroud)
为什么for循环不在300处终止?此程序也始终终止4169.为什么4169而不是其他一些价值?
我最近读到C和C++中的带符号整数溢出会导致未定义的行为:
如果在评估表达式期间,结果未在数学上定义或未在其类型的可表示值范围内,则行为未定义.
我目前正试图了解这里未定义行为的原因.我认为这里发生了未定义的行为,因为当整数变得太大而无法适应底层类型时,整数开始操纵自身周围的内存.
所以我决定在Visual Studio 2015中编写一个小测试程序,用以下代码测试该理论:
#include <stdio.h>
#include <limits.h>
struct TestStruct
{
char pad1[50];
int testVal;
char pad2[50];
};
int main()
{
TestStruct test;
memset(&test, 0, sizeof(test));
for (test.testVal = 0; ; test.testVal++)
{
if (test.testVal == INT_MAX)
printf("Overflowing\r\n");
}
return 0;
}
Run Code Online (Sandbox Code Playgroud)
我在这里使用了一个结构来防止Visual Studio在调试模式下的任何保护问题,比如堆栈变量的临时填充等等.无限循环应该导致几次溢出test.testVal,确实如此,除了溢出本身之外没有任何后果.
我在运行溢出测试时查看了内存转储,结果如下(test.testVal内存地址为0x001CFAFC):
0x001CFAE5 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x001CFAFC 94 53 …Run Code Online (Sandbox Code Playgroud) 如果允许在输入缓冲区末尾读取少量数据,则可以(并且)简化在高性能算法中找到的许多方法.这里,"少量"通常意味着W - 1超过结束的字节,其中W是算法的字节大小(例如,对于处理64位块中的输入的算法,最多7个字节).
很明显,写入输入缓冲区的末尾通常是不安全的,因为您可能会破坏缓冲区1之外的数据.同样清楚的是,在缓冲区的末尾读取到另一页面可能会触发分段错误/访问冲突,因为下一页可能不可读.
但是,在读取对齐值的特殊情况下,页面错误似乎是不可能的,至少在x86上是这样.在该平台上,页面(以及因此内存保护标志)具有4K粒度(较大的页面,例如2MiB或1GiB,可能,但这些是4K的倍数),因此对齐的读取将仅访问与有效页面相同的页面中的字节缓冲区的一部分.
这是一个循环的规范示例,它对齐其输入并在缓冲区末尾读取最多7个字节:
int processBytes(uint8_t *input, size_t size) {
uint64_t *input64 = (uint64_t *)input, end64 = (uint64_t *)(input + size);
int res;
if (size < 8) {
// special case for short inputs that we aren't concerned with here
return shortMethod();
}
// check the first 8 bytes
if ((res = match(*input)) >= 0) {
return input + res;
}
// align pointer to the next 8-byte …Run Code Online (Sandbox Code Playgroud) 我认为首先从0开始400*400=160000转换为28928并以循环方式转换为160000时间为int类型(比如sizeof(int)= 2字节),假设它如下:
然后将28928除以400,其中得到72,结果随变量的类型而变化.我的假设是正确的还是有其他解释?
以下面的C代码为例:
int main(int argc, char *argv[])
{
signed char i;
unsigned char count = 0xFF;
for (i=0; i<count;i++)
{
printf("%x\n", i);
}
return 0;
}
Run Code Online (Sandbox Code Playgroud)
此代码在无限循环中运行,即使我按如下方式编译它:
# gcc -Wall -Wpedantic -Wconversion -Wsign-compare -Wtype-limits -Wsign-conversion test.c -o test
Run Code Online (Sandbox Code Playgroud)
有人知道应该警告这些问题的编译器标志吗?
为了清楚起见,我不是在问'为什么会得到一个无限循环',而是要知道是否有办法使用编译器或静态分析来阻止它?
我只是想知道灾难性的整数溢出是多么的真实.采用以下示例程序:
#include <iostream>
int main()
{
int a = 46341;
int b = a * a;
std::cout << "hello world\n";
}
Run Code Online (Sandbox Code Playgroud)
由于a * a32位平台上的溢出和整数溢出触发了未定义的行为,我是否有任何hello world实际出现在屏幕上的保证?
我根据以下标准引文从我的问题中删除了"已签名"部分:
(§5/ 5 C++ 03,§5/ 4 C++ 11)如果在评估表达式期间,结果未在数学上定义或未在其类型的可表示值范围内,则行为未定义.
(§3.9.1/ 4)声明的无符号整数
unsigned应遵守算术模2 ^ n的定律,其中n是该特定整数大小的值表示中的位数.这意味着无符号算术不会溢出,因为无法由结果无符号整数类型表示的结果以比模式生成的无符号整数类型所表示的最大值大1的数量为模.
我想知道以下代码的使用
int result = 0;
int factor = 1;
for (...) {
result = ...
factor *= 10;
}
return result;
Run Code Online (Sandbox Code Playgroud)
如果循环是随着n时间反复进行的,则将factor其乘以10精确的n时间。但是,factor仅在乘以10总n-1次数后才使用。如果我们假设factor除了在循环的最后一次迭代之外永远不会溢出,而是可能在循环的最后一次迭代中溢出,那么这样的代码是否可以接受?在这种情况下,factor证明溢出后永远不会使用的值。
我正在就是否应接受此类代码进行辩论。可以将乘法放在if语句中,并且在可能溢出时,不对循环的最后一次迭代进行乘法。缺点是它会使代码混乱,并添加了一个不必要的分支,需要在所有先前的循环迭代中进行检查。我还可以减少循环迭代一次,并在循环之后复制一次循环主体,这又使代码复杂化。
有问题的实际代码在一个紧密的内部循环中使用,该循环在实时图形应用程序中消耗大量的总CPU时间。
我刚刚决定改变所有变量unsigned,int并在重新编译有问题的代码时受到此警告消息的欢迎:
freespace_state.c:203: warning: assuming that the loop is not infinite
Run Code Online (Sandbox Code Playgroud)
有问题的一行:
for (x = startx; x <= endx; ++x, ++xptr)
Run Code Online (Sandbox Code Playgroud)
这个循环是60行代码(包括空格/括号等),并且goto在其中有一个,并且至少出现一次continue.
在这种情况下,我认为我很欣赏GCC假设这个循环不是无限的,因为它永远不应该无限循环.
GCC试图在这里告诉我什么?
警告的语法几乎暗示警告应该在某些其他警告的范围内进行,但在该背景下没有警告.
[编辑] 这完全是我自己的错.我从这里的一个问题偷了一些优化和警告选项而没有真正理解它们,并且从此忘记了它们.
请参阅Mark Rushakoff的回答,此外,我还习惯-Wunsafe-loop-optimizations明确警告GCC是否对循环做出假设.请参阅http://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html
c++ ×7
c ×5
gcc ×3
x86 ×2
assembly ×1
gcc-warning ×1
operators ×1
optimization ×1
performance ×1
signed ×1
types ×1
unsigned ×1