云计算百科
云计算领域专业知识百科平台

(学习笔记)2.2 整数表示(2.2.8 关于有符号数与无符号数的建议)

目录

  • 线索栏
  • 笔记栏
    • 1. 隐式转换的陷阱与核心警告
    • 2. 练习题 2.25 (数组求和 Bug)
      • 1)Bug代码
      • 2)错误分析
      • 3)修复方案
    • 3. 练习题 2.26 (字符串比较 Bug)
      • 1)Bug代码
      • 2)错误分析
      • 3)修复方案
    • 4. 真实漏洞分析:FreeBSD getpeername
      • 1)漏洞代码简化
      • 2)漏洞成因
      • 3)修复方法
    • 5. 核心建议与合理使用场景
      • 1)核心建议(黄金法则)
      • 2)合理且有益的特定场景
  • 总结栏

线索栏

  • 为什么说从有符号数到无符号数的隐式强制类型转换是危险的?
  • 练习题 2.25​ 中,当 length = 0时,循环 for (i =0; i <= length-1; i++)为什么会导致内存错误?
  • 练习题 2.26​ 中,函数strlonger的bug是什么?在什么条件下会产生错误结果?
  • FreeBSDgetpeername函数漏洞的根本原因是什么?它与数据类型不匹配有何关联?
  • 为了避免此类错误,关于使用无符号数的核心建议是什么?
  • 在哪些特定场景下,使用无符号数是合理且有益的?

  • 笔记栏

    1. 隐式转换的陷阱与核心警告

    (1)核心警告:C语言中,从有符号数到无符号数的隐式强制类型转换是许多非直观行为和程序错误的根源。由于转换悄无声息地发生,程序员极易忽视其影响,导致难以发现的细微错误。 (2)原理:当有符号数和无符号数在表达式中混合运算时,C语言会将有符号数隐式地转换为无符号数,然后按无符号规则进行运算。这会导致负数被解释为极大的正数,彻底改变比较和算术运算的逻辑。

    2. 练习题 2.25 (数组求和 Bug)

    在这里插入图片描述

    1)Bug代码

    float sum_elements(float a[], unsigned length) {
    int i;
    float result = 0;
    for (i = 0; i <= length 1; i++)
    result += a[i];
    return result;
    }

    2)错误分析

    (1)当 length = 0时,表达式 length – 1的结果应为 -1。 (2)但由于 length是 unsigned类型,整个表达式会进行无符号运算:0U – 1U。 (3)在无符号模运算下,0U – 1U的结果并非 -1,而是通过下溢得到最大值​ UMax(对于32位是4294967295)。 (4)循环条件 i <= UMax恒成立,导致程序试图访问数组 a的非法内存(a[0], a[1], …),最终引发内存错误。

    3)修复方案

    (1)最佳方案:将循环条件改为 i < length。这是最常见且安全的写法。 (2)次选方案:将参数 length的类型改为 int。但会改变函数接口。 在这里插入图片描述

    3. 练习题 2.26 (字符串比较 Bug)

    在这里插入图片描述

    1)Bug代码

    int strlonger(char *s, char *t) {
    return strlen(s) strlen(t) > 0;
    }

    2)错误分析

    (1)strlen的返回类型是 size_t(在标准库中通常定义为 unsigned int或 unsigned long)。 (2)当 strlen(s) < strlen(t)时,表达式 strlen(s) – strlen(t)会进行无符号减法,结果是下溢后的大正数,导致比较 > 0恒为真,函数错误地返回 1(表示s更长)。

    3)修复方案

    (1)直接比较长度,避免做差:return strlen(s) > strlen(t); (2)这是安全可靠且意图清晰的做法。 在这里插入图片描述

    4. 真实漏洞分析:FreeBSD getpeername

    1)漏洞代码简化

    #define KSIZE 1024
    char kbuf[KSIZE];
    int copy_from_kernel(void *user_dest, int maxlen) { // maxlen 为有符号 int
    int len = KSIZE < maxlen ? KSIZE : maxlen;
    memcpy(user_dest, kbuf, len); // memcpy的第三个参数 n 是无符号 size_t
    return len;
    }

    2)漏洞成因

    (1)数据类型不匹配:函数参数 maxlen为有符号 int,但库函数 memcpy的第三个参数 n是无符号 size_t。 (2)恶意参数触发:攻击者传入一个负值(如 -1)给 maxlen。 (3)隐式转换与下溢:在第16行,负的 maxlen与 KSIZE(1024) 比较时,会先被隐式转换为无符号数,变成一个极大的正数(接近 UMax)。因此 len被赋值为这个极大值。 (4)溢出执行:这个极大的 len作为 size_t传递给 memcpy,导致函数尝试复制海量数据(远超目标缓冲区大小),可能读取未授权的内核内存,造成信息泄露。

    3)修复方法

    统一类型,消除不匹配:将 copy_from_kernel的参数 maxlen、局部变量 len和返回值类型都改为 size_t。

    5. 核心建议与合理使用场景

    1)核心建议(黄金法则)

    除非在必要且能清晰掌控的场景下,否则尽量避免使用无符号数。​ 许多现代语言(如 Java、Python)的设计就完全放弃了无符号整数类型,正是为了避免此类隐晦错误。

    2)合理且有益的特定场景

    (1)位集合与标志:当把变量纯粹当作位的集合来操作时(如设置、清除标志位、掩码运算)。 (2)内存地址:地址计算本质上是模运算,使用无符号数非常自然。 (3)模运算:在需要显式利用模运算特性的算法中。 (4)多精度算术:当大整数需要用基本数据类型的数组来表示时,每个数组元素可以作为无符号的“数字”。


    总结栏

    本节通过理论分析和实例(练习题、真实漏洞)深刻揭示了无符号数与有符号数混用的巨大风险:

  • 隐式转换是隐形杀手:表达式 -1 < 0U结果为 0(假),这与数学直觉完全相悖。此类错误在循环和边界检查中尤其致命。
  • 接口不匹配滋生漏洞:系统级编程中,函数参数和库函数参数之间的类型不匹配是安全漏洞的温床(如 getpeername漏洞)。
  • 防御性编程准则: (1)慎用无符号:默认使用有符号类型(如 int),除非明确需要无符号特性。 (2)统一类型:在表达式中保持操作数类型一致,避免混合运算。 (3)显式比较:用 x > y代替 x – y > 0,消除下溢风险。 (4)仔细审查:对涉及无符号数、size_t以及与标准库交互的代码进行严格审查,特别关注类型是否匹配。
  • 根本启示:计算机系统中的数据类型不仅是值的容器,更是运算规则的契约。无符号数的算术是模运算,而非通常的整数运算。忽视这一语义差异,高层抽象就会“泄漏”,导致逻辑错误和安全漏洞。编写健壮代码的关键在于:理解并尊重每个类型的语义,并在边界处保持警惕。

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » (学习笔记)2.2 整数表示(2.2.8 关于有符号数与无符号数的建议)
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!