我正在学习C ++,阅读Stroustrup"原理与实践使用C ++"。
在有关前置和后置条件的部分中,有以下功能示例:
$('button.btn.btn-success').click(function(event)
{
event.preventDefault();
$.post('getpostcodescript.php', $('form').serialize(), function(data, status, xhr)
{
// do something here with response;
console.info(data);
console.info(status);
console.info(xhr);
})
.done(function() {
// do something here if done ;
alert( "saved" );
})
.fail(function() {
// do something here if there is an error ;
alert( "error" );
})
.always(function() {
// maybe the good state to close the modal
alert( "finished" );
// Set a timeout to hide the element again
setTimeout(function(){
$("#myModal").hide();
}, 3000);
});
});
让我感到困惑的是关于此代码的任务:
找到一对值,使这个版本的前提条件 区域成立,但后置条件没有。
是否存在这样的可能的整数值,前提条件是好的但条件不是吗?
答案 0 :(得分:33)
是否存在这样的可能的整数值,前提条件是好的但条件不是吗?
是的,有许多输入值,可能导致后置条件失败。如果是这样的话。
int a = length*width;
溢出正int
范围(std::numeric_limits<int>::max()
),编译器实现会为此情况产生负值。
正如其他人在他们的回答中所指出的那样,length*width
超出]0-std::numeric_limits<int>::max()[
范围的情况实际上是未定义的行为,并且后置条件呈现的仅仅是无用的,因为可能需要预期任何值a
。
解决此问题的关键点在@Deduplicator的answer中给出,前提条件需要改进。
作为Bjarne Stroustrup给出这个例子的理由的长矛:
我认为他想指出,这种未定义的行为可能会导致后置条件中出现意外的负值,并且会出现令人惊讶的结果,因为在前置条件下检查了一个天真的假设。
答案 1 :(得分:27)
不,在标准C ++定义的行为范围内,没有任何值会违反后置条件。但是,有些值仍然可以使函数表现不正确,即值太大而其产品不适合整数。尝试通过200&000;和15&#39;
由于大多数编译器实现C ++的方式,您可能会看到后置条件被违反,但您实际观察到的是由于整数溢出而导致的未定义行为。
答案 2 :(得分:12)
答案是他的前提条件检查不完整。即使它太严格了。
他未能检查产品是否可以代表而不是产生UB:
int area(int length, int width) {
// calculate area of a rectangle
assert(length >= 0 && width >= 0 && (!width
|| std::numeric_limits<int>::max() / width >= length));
int a = length * width;
assert(a >= 0); // Not strictly neccessary - the math is easy enough
return a;
}
答案 3 :(得分:5)
我想到的是签名溢出。这是未定义的行为,但可能产生负值
试试std::numeric_limits<int>::max()
和2
。
答案 4 :(得分:4)
是的,如果假设您使用的是16位计算机,那么int = 2B最大值+32767,所以在下面
{
length = 500, width = 100;
if (length<=0 || width <=0) error("area() pre-condition");
int a = length*width; // a = 500 * 100 = 50000
if (a<=0) error("area() post-condition");
return a;
}
现在最终值为a = -17233
,因为它会进入-ve值。
所以第二个条件变得虚假。
一切都取决于范围。
答案 5 :(得分:3)
INT_MAX
将无法满足后置条件。
有人可能会说,由于标准保证INT_MAX
&gt; = 32767,因此INT_MAX*INT_MAX
将始终大于INT_MAX
,因此无法在{{int
中表示1}}被定义为能够保持最大值INT_MAX
这是一个很好的论据,实际上最常发生的事情是,大多数编译器都会出现溢出。
但要涵盖所有基础,我们需要注意C++ standard状态:
<强> 3.4.3 强>
1未定义的行为
行为,使用不可移植或错误的程序结构或错误数据,本国际标准不对此作出任何要求2注意可能的未定义行为包括完全忽略具有不可预测结果的情况,在翻译或程序执行期间以环境特征记录的方式执行行为 (有或没有发出诊断消息),终止翻译或执行(发布诊断消息)。
3示例未定义行为的示例是整数溢出的行为。
所以它比没有为该地区获得正确的价值更严重。当INT_MAX
用于长度和宽度(或任何其他具有无法表示的结果的组合)时,无法保证编译的程序将执行的操作。任何事情都可能发生;从可能的溢出或崩溃到不太可能的那些,如磁盘格式。
答案 6 :(得分:3)
溢出值类型的位表示的值的乘法是未定义的,因为溢出的位数可能大于1.因此,您可能最终得到正或负符号位,并且丢失位的数量是可变的。 / p>
示例1:INT_MAX * 2
:结果是正确的,但由于高位表示符号位,因此不会根据其类型进行更正。
示例2:INT_MAX * 4
:溢出丢失1位,符号位不正确,如上例所示。
示例3:(INT_MAX + 1) * 2 = 0
:由于所有设置位溢出但符号正确。
我正在使用8位二进制表示来使其更容易阅读,以说明为什么会发生这种情况。
0111 1111 // Max positive signed value
+1
1000 0000 // Sign bit set but binary value is correct
*2
0000 0000 // Upper bit is lost due to overflow
在这种情况下,软溢出,没有丢失信息,但表示不正确。并且在结果中不再出现该位的硬溢出。
溢出的不同之处在于如何检测溢出。通常硬件将检测到硬溢出,并且需要很少的工作来使软件处理。但是,软件溢出可能需要软件明确测试溢出条件,因为硬件通常无法识别整数数学运算中的符号位。
运行时库如何处理溢出取决于库。大多数人会忽略它,因为这样做会更快,而其他人可能会抛出错误。 未定义的行为并不意味着它可能会格式化您的磁盘。除了代码的逻辑指示之外,数学运算的结果不会以任何方式改变代码流。它可以忽略溢出或尝试以某种方式处理它。如果代码或硬件试图处理问题,标准没有规定采用什么方法。
基本上有三种可能发生的事情 1.溢出被忽略,返回值无效 2.运行时库忽略溢出但硬件抛出的错误也被忽略,导致运行代码出现硬故障。在这种情况下,操作系统完全可以确定接下来会发生什么。坚持不懈并破坏数据将导致糟糕的设计决策 3.溢出由运行时库处理,该库必须确定最佳的继续方式。通常,这意味着让代码有机会捕获错误并处理错误,或者通过尽可能优雅地关闭代码。
答案 7 :(得分:3)
从C ++ 11开始,你可以测试一个布尔值:
std::numeric_limits<int>::is_modulo
如果此值为true
,则签名算术将以环绕方式运行,并且原始代码中没有未定义的行为。确实可以产生负值,因此原始代码中的测试是有意义的。
有关is_modulo
see here
答案 8 :(得分:0)
基本上,乘法中的正值...导致正值但这些实际上可能不适合结果类型。
您的前提条件未完成,您的后置条件也无效。您不仅可以获得负值,还可以获得仅小于输入值的正值,您只需将足够大的值作为输入,使得环绕超过零,即 long-wrap-around 。
您可以使用this:
bool multiplication_is_safe(uint32_t a, uint32_t b) {
size_t a_bits=highestOneBitPosition(a), b_bits=highestOneBitPosition(b);
return (a_bits+b_bits<=32);
}
防止溢出,但是你会想要对FALSE-Positives采用额外的检查。
或者,如果性能不是很大的问题,您可以使用MPZ库。如果性能是一个问题,并且您想为具有溢出标志的CPU编写程序集,那么您可以这样做。您的编译器也可以为您进行检查,例如在前置条件检查之后,G ++有fno-strict-overflow
或者可能被转换为unsigned int
。
无论如何,大多数这些解决方案实际上并没有解决您的问题,结果将是 foo ,即您可能会获得比实际结果更小的区域。
因此,您唯一安全的选择是只允许安全乘法,如此处所示,这样做会错过一些东西,但不会那么多。