csapp-homework-2.74

这里挂一下csapp的2.74问题和答案,进行分析,学到了条件判断然后赋值的新思路。

Write a function with the following prototype:

/* Determine whether arguments can be subtracted without overflow */

	int tsub_ok(int x, int y);

This function should return 1 if the computation x-y does not overflow

首先这个题目并没有要求使用164页的编程规则限制,也就是说你其实可以用条件判断和循环来实现这个题目。然后另一个问题就是,怎么样才能更简单实现这个功能。

我的思考肯定是基于这两个判断表达式的:

if(x < 0 && y > 0 && sub > 0) return 0; //负溢出
if(x > 0 && y < 0 && sub < 0) return 0; //正溢出

然后可以看看答案是怎么考虑这个问题的,之所以记录这个题目,就是因为这个题目运用了逻辑运算与&&的短路特性来使得代码简化了(随之而来的是可读性变差的结果):

/*
 * tsub-ok.c
 */
#include <stdio.h>
#include <assert.h>
#include <limits.h>

/* Determine whether arguments can be substracted without overflow */
int tsub_ok(int x, int y)
{
    int res = 1;
    /* 如果y为INT_MIN,则x为任意值都会溢出:
    x > 0,sub < 0; 
    x < 0(x此时取值范围为 [INT_MIN,0) ),sub >= 0--突然发现这个条件是判断是否溢出的一个定理,可以重点记忆一下
    */
    (y == INT_MIN) && (res = 0);
    // if (y == INT_MIN) res = 0;

    /*这里就是很正常的条件判断,但是利用了逻辑与 && 运算符的短路特性*/
    int sub = x - y;
    int pos_over = x > 0 && y < 0 && sub < 0;
    int neg_over = x < 0 && y > 0 && sub > 0;

    res = res && !(pos_over || neg_over);

    return res;
}

int main(int argc, char* argv[]) {
  assert(!tsub_ok(0x00, INT_MIN));
  assert(tsub_ok(0x00, 0x00));
  return 0;
}

可以看到这个题目中,用到了逻辑运算与 && 运算符的短路特性来判断是否溢出了。用chatgpt来分析这个 (y == INT_MIN) && (res = 0);语句实现了什么功能,可以得到答案:

这个表达式使用了逻辑与 && 运算符,它具有短路特性。表达式中有两部分:

  1. (y == INT_MIN):检查变量 y 是否等于 INT_MIN,即判断 y 是否为整型最小值。
  2. (res = 0):将变量 res 赋值为 0

这两部分由 && 连接在一起,整个表达式的结果取决于两部分的值。

如果第一部分 (y == INT_MIN) 为真(即 y 等于 INT_MIN),那么第二部分 (res = 0) 也会被执行。这是因为在逻辑与运算中,如果第一个条件为真,那么第二个条件就会被检查并执行。在这种情况下,整个表达式的值为真。

如果第一部分 (y == INT_MIN) 为假,那么第二部分 (res = 0) 将不会被执行,因为短路特性会导致整个表达式的值为假。

这样的表达式通常用于条件赋值的场景。如果 y 的值等于 INT_MIN,则将 res 的值设置为 0;否则,保持 res 的原始值。在实际编码中,这种写法可能会让代码可读性变差,应该慎用。最好将条件判断和赋值操作分开,以提高代码的清晰度。

那么其实这个题目用了某种取巧的方法简化了代码结构,其实可读性变差了。但是仍然加强了对于逻辑运算符的理解。

其实2.74还蕴藏着一个知识点:两个数的和什么时候会溢出。

当x + y > TMAX,或者x + y < TMIN时,这时候就会导致溢出。若根据这两个判断条件来确定当溢出时x和y的范围,则需要分类讨论:

  1. x > 0时,因为TMIN < y < TMAX,所以x + y > TMIN + x是永远不会负溢出的。但是可能会出现x+ y > TMAX的正溢出的场景。此时x > 0,那么y的取值范围就是(TMAX - x, TMAX]
  2. x < 0时,x + y = TMAX + x永远不会正溢出。但是会出现负溢出(x + y < TMIN)的场景。y的取值范围是[TMIN, TMIN - x)

然后回顾2.74题目,发现在最后返回是否溢出的时候,两个溢出标志用了逻辑运算符||

  res = res && !(pos_over || neg_over);

在这分析下这句话的作用:

首先是括号内的pos_over || neg_over,这里的意思是:当正溢出或者负溢出时候,此条件成立。即只要是溢出了,则此括号内的运算结果为1。

那此时!(pos_over || neg_over)就相当于一个mask:若计算结果溢出,则返回0;若不溢出,则返回1。

那我有个疑问,这个mask就是一个根据是否正溢出或者负溢出而判断溢出的标志掩码,那么我能否在括号内将||换为&&

那此时只要有一个溢出标志为假,那么这个mask的取值就会成为:若两边都溢出,则返回0;若其中有一个溢出或者都不溢出,则返回1。这样与题目的要求不符合:只要溢出,则返回0。

所以这个||不能换为&&

(存货,写于2024年2月2号)

posted @ 2024-02-20 19:03  上山砍大树  阅读(7)  评论(0编辑  收藏  举报