从语言基础进入系统接口

系统入口:位模式与接口返回值

用1、2和258读懂位掩码与字节编码,再区分地址与活对象、请求长度与实际返回量。

4个完整小节预计4核心小时,含阅读与编码

先完成完整G0的独立验收,再进入本页。读完基础章、运行教材示例或打开本页都不会自动通过G0。

C++20;前两个程序要求uint8_t/uint32_t可用,unsigned int有32个值位、int有31个值位。返回量程序使用POSIX的ssize_t名称,但没有调用write或模拟操作系统;所有返回值均为明确手填的小整数。真实mmap、系统短写、释放后访问和非法移位均不在这些源码中执行。。时间是设计估算,按完整小节推进;用自己的输入、代码、输出与疑问记录学习过程。

这一页要能做到

  • 区分按位与逻辑运算,用掩码合并、检查、翻转和清除指定位置。
  • 在已检查的整数环境中解释提升、32位移位、8位窄化与258的四字节小端编码。
  • 通过仍存活的原类型对象演示void*往返,不把地址转换当作创建或延长对象寿命。
  • 先检查带符号返回值与请求边界,再更新无符号偏移;区分手填返回量模型与真实POSIX运行。

先预测,再运行;一次只改一个条件,最后离开示例独立完成,并复述原因。下面的源码与下载是同一份文件,图解和完整文字可逐步对照。

开始前:把整数、地址和系统接口连起来

这一单元放在完整G0之后、系统章节之前,预计4核心小时,包含阅读、推演与编码。基础语法沿用新基础01–18;这里补的是系统代码真正开始用到的位运算、地址类型和返回值合同。学习过程中仍先写预测,再运行,只改一个条件,最后独立完成与复述。

本单元有四份独立程序。前三份使用标准C++20;第四份通过POSIX头文件取得ssize_t,但只运行返回值模型。它不发起系统调用,也不证明你的机器发生过短写。下载后在文件所在目录编译,例如:

clang++ -std=c++20 -Wall -Wextra -Wpedantic -Werror bit-masks.cpp -o bit-masks && ./bit-masks
echo $?

编译命令的读法见基础17,第一次保存文件见编译指南。每份文件都有自己的main,分别生成程序;不要把四份一起链接。以下布尔输出仍采用默认的0表示假、1表示真。

S1 · 一个整数,怎样保存几个开关?

假设我们自己约定三个开关:读为1,写为2,执行为4。它们分别占据二进制的第0、1、2位;位号从最低位的0开始。十进制3可以写成低八位00000011,表示读和写都打开。这种用每一位表示一个条件的整数叫位掩码(bit mask)。本例数字是教学约定,系统头文件中的权限或事件常量必须使用其真实定义,不能照搬1、2、4。

<cstdint>提供固定宽度整数名称:std::uint32_t表示恰好32位的无符号整数,std::uint8_t表示恰好8位的无符号整数。这些精确宽度类型在标准中是可选的,本单元要求环境提供它们。32位无符号值的范围是0到4294967295。整数后缀U与已学的u等价,表示无符号字面量。

后续主线还使用同一头文件的std::int64_tstd::uint64_t:前者是恰好64位的有符号整数,范围−9223372036854775808到9223372036854775807;后者是恰好64位的无符号整数,范围0到18446744073709551615。它们同样是实现提供相应精确类型时才存在的名称,本教材的这些程序要求提供它们。名字的64描述类型宽度,不代表它们永远不会溢出。

例如把本来只有250与10的输入各自保存进有符号64位累加器,和260在范围内;对任意长输入则还必须证明所有中间和都在范围内。无符号累加按其位宽回绕,不能悄悄把它当成无限精度数学求和。Reduction Lab使用有界输入合同,测量例中的无符号checksum也需要单独判断能支持什么结论。本类型补充另估10分钟,未纳入SYS-00原4小时。

先把四个运算符按“一位对一位”理解,再计算整个整数。它们不会短路;不要和控制流中的&&||混淆。

运算 一位的规则 本例结果
a | b,按位或 两边至少一位为1,这一位就为1 1与2合并得到3
a & b,按位与 两边这一位都为1才保留1 3筛选2得到2,3筛选4得到0
a ^ b,按位异或 两边这一位不同才为1 3翻转掩码4指定的位,得到7
~a,按位取反 把参与运算类型的每一位翻转 本例32位的1取反得到4294967294

位操作会先进行整数提升:比int等级低的小整数类型,如果全部值都能用int表示,就先变为int;否则提升为unsigned int。运算的位宽必须看提升后的类型,不能只看变量名里写了几位。本程序先检查unsigned int有32个值位、int有31个值位;numeric_limits::digits不把有符号类型的符号位算进去。不符合这个条件时程序明确拒绝运行这套数字示例,而不是假装所有机器的整数宽度相同。

对本例的32位无符号数取反,必须翻转全部32位。图中只画低八位时,~1的高24位也是1;把结果说成254就丢掉了这些位。只有随后明确转换到8位无符号数时,才会只留下低八位,转换规则在下一节讲。

flags |= write_bit是复合按位赋值:对这里的普通变量,相当于把flags | write_bit的结果写回flagsflags & mask得到的仍是整数;写成(flags & mask) != 0U,才明确表示“至少命中一个指定的位”。如果想检查掩码中的位是否全部存在,使用(flags & mask) == mask;掩码为0时这个全包含条件自然为真。

清除某一位可以先把掩码取反,再做与:toggled & ~write_bit保留其他位,把写位清零。下面先推演flags的1→3,再算3 ^ 4得到7、7 & ~2得到5。不要把按位或当作普通加法:3与2按位或仍是3,3加2则是5。

观察 · 推演

32位掩码:低8位只是局部视图

read00000001write00000010组合00000011 = 301 / 04 · MEMORYread00000001write00000010组合00000011 = 301 / 04 · MEMORY
1 · 组合两个位

read=1、write=2,按位或得到3。此帧只显示最低8位,其余24位均为0。

1 / 4
阅读完整推演文字
  1. 1 · 组合两个位

    read:00000001;write:00000010;组合:00000011 = 3

    read=1、write=2,按位或得到3。此帧只显示最低8位,其余24位均为0。

  2. 2 · 检查掩码

    flags:3;检查write:2 ≠ 0 → true;检查execute:0 → false

    3 & 2得到2,判断非零为true;3 & 4得到0,为false。位与本身先产生整数,比较再产生bool。

  3. 3 · 翻转后清除

    异或4:00000111 = 7;清除2:00000101 = 5;保留位:read + execute

    3 ^ 4得到7,再用7 & ~2清除write位得到5。其余24位仍为0;操作不会把所有位都清零。

  4. 4 · 全宽取反

    低8位:11111110;高24位:全部为1;完整结果:4294967294

    对32位uint32_t的1取反,低8位是11111110,另外24个高位全是1,完整值是4294967294。

#include <cstdint>
#include <iostream>
#include <limits>

int main() {
    if (std::numeric_limits<unsigned int>::digits != 32 ||
        std::numeric_limits<int>::digits != 31) {
        std::cout << "unsupported-integer-environment\n";
        return 1;
    }
    const std::uint32_t read_bit{1U};
    const std::uint32_t write_bit{2U};
    const std::uint32_t execute_bit{4U};
    std::uint32_t flags{read_bit};
    flags |= write_bit;
    const bool has_write{(flags & write_bit) != 0U};
    const bool has_execute{(flags & execute_bit) != 0U};
    std::cout << "combined=" << flags << " has-write=" << has_write
              << " has-execute=" << has_execute << '\n';
    const std::uint32_t toggled{flags ^ execute_bit};
    const std::uint32_t cleared{toggled & ~write_bit};
    const std::uint32_t inverted_read{~read_bit};
    std::cout << "toggled=" << toggled << " cleared=" << cleared << '\n';
    std::cout << "inverted-read=" << inverted_read << '\n';
    if (!has_write || has_execute || (cleared & write_bit) != 0U ||
        (cleared & execute_bit) == 0U) {
        return 1;
    }
    return 0;
}

应观察到组合值3、具有写位为1、具有执行位为0;翻转后7,清位后5,读位取反4294967294。只把flags的初始值从read_bit改为0,再预测:合并后是2,翻转后是6,清位后是4;是否有写位、是否有执行位的两次判断发生在翻转之前,仍是1和0。

S2 · 怎样把258拆成四个字节,再拼回来?

整数两边的<<表示左移,不是std::cout << value中的流输出。无符号左移把位向高位移动,低位补0;超出结果位宽的高位被丢弃。无符号右移向低位移动,高位补0。例如32位无符号的1左移1位是2,258右移8位是1。

移位量必须非负,并且小于提升后左操作数的位宽。本例宽度32,因此0和31合法,32不合法;移32位不是“自动变成0”,也不应实际运行它试运气。下面的shift_left先判断移位量,拒绝时返回false,且没有写入引用参数output;只有接受后才执行移位并写入结果。这是一个普通的“成功标志+输出参数”接口,引用读法沿用基础05。

用32位无符号数计算时,2147483648最高位为1;它左移1位后高位丢失,结果是0。这是这个无符号运算的明确规则,不代表任意有符号算术都可以忽略溢出。后面读64位编码时,同样要使用相应宽度的操作数,合法移位量改为0到63,不能继续硬写32。

现在约定一种小端编码(little-endian encoding):数组的第0个元素保存最低八位,第1个元素保存接下来的八位。这里只是在设计字节序列,不读取CPU内存中的整数表示,因此不依赖主机自身采用哪种字节序。

字节下标 先右移 再与255筛选最低八位 存下的十进制值
0 258右移0位→258 258的最低八位 2
1 258右移8位→1 1的最低八位 1
2 258右移16位→0 0的最低八位 0
3 258右移24位→0 0的最低八位 0

255的低八位全是1,所以与255能只保留这八位。转换到std::uint8_t时,值已经处于0–255;若明确转换一个更大的整数到8位无符号类型,则结果按256取模,例如258转换后是2。这个规则不能用于解释浮点转换,也不表示信息没有丢失;大括号对窄化初始化的限制仍然有效,程序通过static_cast明确请求这次整数转换。

拼回时先把每个字节转换到std::uint32_t,再左移0、8、16、24位,最后逐个按位或。特别要注意,uint8_t参加运算通常先提升为int;“变量只有八位,所以移位也按八位进行”是错误的。本程序明确选择32位无符号的中间运算,避免让小整数的提升决定编码合同。

程序中打印字节前又转换成unsigned int,因为uint8_t可能就是字符类型,直接送到输出流可能显示字符;这里要查看数值2、1、0、0。用十六进制记号写同一串字节是02 01 00 00,但程序下面实际打印的是十进制。

观察 · 推演

258的编码顺序与合法移位边界

原值258最低字节2下一字节101 / 05 · PIPELINE原值258最低字节2下一字节101 / 05 · PIPELINE
1 · 先求两个有效字节

258 = 1×256 + 2。编码约定低有效字节在前;2和1的十六进制写法是02和01。

1 / 5
阅读完整推演文字
  1. 1 · 先求两个有效字节

    原值:258;最低字节:2;下一字节:1

    258 = 1×256 + 2。编码约定低有效字节在前;2和1的十六进制写法是02和01。

  2. 2 · 四项完整编码

    i=0:2;i=1:1;i=2 / i=3:0 / 0

    value右移0/8/16/24位后分别与255相与,得到2/1/0/0。输出是十进制,不是读取主机内存所得的字节顺序。

  3. 3 · 先扩宽,再还原

    低位贡献:2;下一字节贡献:1 << 8 = 256;合并结果:258

    先把每个uint8_t值转为uint32_t,再移动0/8/16/24位。2 | 256得到258。

  4. 4 · 0与31合法

    amount=0:1;amount=31:2147483648;output:更新为最后结果

    32位左操作数的合法非负移位量是0至31。shift_left(1,0)得到1;shift_left(1,31)得到2147483648。

  5. 5 · 32先拒绝

    amount=32:拒绝,不移位;output保留:2147483648;最高位左移1:合法,得到0

    amount=32在实际移位之前返回false,output仍为2147483648。另一个合法操作2147483648 << 1得到0,移出截断和移位量越界不同。

#include <array>
#include <cstddef>
#include <cstdint>
#include <iostream>
#include <limits>

bool shift_left(std::uint32_t value, unsigned int amount, std::uint32_t& output) {
    if (amount >= 32U) {
        return false;
    }
    output = value << amount;
    return true;
}

int main() {
    if (std::numeric_limits<unsigned int>::digits != 32 ||
        std::numeric_limits<int>::digits != 31) {
        std::cout << "unsupported-integer-environment\n";
        return 1;
    }
    const std::uint32_t value{258U};
    std::array<std::uint8_t, 4> bytes{};
    for (std::size_t i{0}; i < bytes.size(); ++i) {
        bytes[i] = static_cast<std::uint8_t>((value >> (8U * i)) & 255U);
    }
    std::uint32_t decoded{0U};
    for (std::size_t i{0}; i < bytes.size(); ++i) {
        decoded |= static_cast<std::uint32_t>(bytes[i]) << (8U * i);
        if (i != 0) {
            std::cout << ' ';
        }
        std::cout << static_cast<unsigned int>(bytes[i]);
    }
    std::cout << "\ndecoded=" << decoded << '\n';
    const std::uint8_t narrowed{static_cast<std::uint8_t>(258U)};
    std::cout << "narrowed=" << static_cast<unsigned int>(narrowed) << '\n';
    std::uint32_t shifted{9U};
    const bool zero{shift_left(1U, 0U, shifted)};
    std::cout << "shift-0=" << zero << " value=" << shifted << '\n';
    const bool last{shift_left(1U, 31U, shifted)};
    std::cout << "shift-31=" << last << " value=" << shifted << '\n';
    const bool rejected{shift_left(1U, 32U, shifted)};
    std::cout << "shift-32=" << rejected << " retained=" << shifted << '\n';
    const std::uint32_t top{2147483648U};
    const std::uint32_t discarded{top << 1U};
    std::cout << "discarded=" << discarded << '\n';
    if (decoded != value || !zero || !last || rejected ||
        shifted != top || discarded != 0U || narrowed != 2U) {
        return 1;
    }
    return 0;
}

第一行应是2 1 0 0,随后还原得到258,窄化结果为2。安全移位依次接受0和31:输出变成1、2147483648;移位量32被拒绝,输出仍保留2147483648。这里的shift-32=0表示调用返回假,不表示执行了移位并得到0。最后的discarded=0才是合法的最高位左移1位后的数值。

只把要编码的value从258改成257,先写下新的四个字节与还原值,再运行。用于单独观察窄化的那一行仍然转换字面量258,所以narrowed应仍为2;不要因为上面的变量变了,就替一个没有读它的表达式更改预测。

S3 · 地址还在,为什么不一定有东西可读?

int*同时提供一个地址和按int访问的类型信息。void*可以保存对象指针的地址,却不指明要按什么对象类型解读它;它不是“可以直接读取任何对象的万能指针”。标准C++不能通过void*直接解引用得到对象值,也不能对它做普通的指针算术,因为不知道一个元素的大小。

一个仍存活的int有指针original。把它放进void* address是对象指针到void*的转换;再用static_cast<int*>(address)恢复int*。在这个来源明确、对象仍活着的往返中,恢复指针指回原对象;不是创建了一个副本。通过它写9,原来的value就变成9。

这种恢复需要有依据:原来确实是相应对象的指针,地址满足该类型的对齐要求,访问的大小在有效范围内,而且对象仍然存活。对齐可以先理解为某种类型对起始地址位置的要求;本例的地址直接来自真实int,天然满足它。不需要背一个具体地址数值,也不能仅凭“有这么多字节”就认定它们已经是任意类型的对象。

static_cast不会分配内存、创建新对象或延长寿命,也不能用它去除本来存在的const访问限制。const void*表示不能通过这条访问路径修改数据;它不是不可更换地址的指针变量。指针本身与所指对象的const区别仍见基础06。

观察 · 推演

同一个活对象,地址转换不创造寿命

value活着,7original指向value01 / 04 · MEMORYvalue活着,7original指向value01 / 04 · MEMORY
1 · 活对象提供地址

value已经建立且值为7;original指向这个int。

1 / 4
阅读完整推演文字
  1. 1 · 活对象提供地址

    value:活着,7;original:指向value

    value已经建立且值为7;original指向这个int。

  2. 2 · 保存为void*

    value:仍为7;address:原地址,void*

    address保存同一地址,丢掉的是这条访问路径的具体对象类型;没有建立另一个int,也不能直接解引用void*读取int。

  3. 3 · 恢复原类型后访问

    restored:原来的int*;指针相等:true;value:变为9

    restored恢复int*并且等于original。通过它写9,修改的是原来的value;源码的访问全部在value活着时。

  4. 4 · 寿命结束后不可延续访问

    原int对象:寿命已结束;旧地址记录:不证明可访问;本次操作:不解引用

    此帧是合同分析,不是源码的运行结果。原int对象寿命结束后,保存过的地址不能证明这个int仍可访问;本章不执行这种错误访问。

#include <iostream>

int main() {
    int value{7};
    int* original{&value};
    void* address{original};
    int* restored{static_cast<int*>(address)};
    std::cout << "same-pointer=" << (restored == original)
              << " value=" << *restored << '\n';
    *restored = 9;
    std::cout << "original-value=" << value << '\n';
    if (restored != original || value != 9) {
        return 1;
    }
    return 0;
}

预测第一行:两个指针相等为1,读取值为7;第二行原对象是9。只把初始value从7改成8,恢复指针读取8,之后的写入仍把原对象变成9。现在只在纸上把value的作用域提前结束:地址变量可能还保存某个值,但已经不能据此读取那个已销毁的对象。不要运行这个失效访问版本。

后面会看到POSIX的mmap返回void*。它有自己的映射合同,应分清三种状态:失败时返回MAP_FAILED这个规定的失败标记;成功后才能按映射的权限和有效范围访问;成功解除映射后,不能再通过旧地址访问原映射。不能只用nullptr判断映射失败,也不能把本例“从活int恢复”的理由直接套到任意映射区域里的任意C++对象上。

这里只先建立地址和有效期的边界,不要求现在预测完整的映射程序。映射参数、页面大小和释放操作继续按虚拟内存章节的具体合同学习;本单元的void*程序没有调用mmap

S4 · 请求写3个字节,为什么只能前进2?

系统接口也可以像普通函数一样先读签名。POSIX规定一组操作系统接口;这里的文件描述符是操作系统交给程序的整数句柄,后面用它指定要操作的文件或通信端点。获取、关闭和实际传输放在下一章,此处先读最小调用合同。

write签名的核心形状是ssize_t write(int fd, const void* buffer, size_t count)fd选择目标,buffer提供可读数据的起始地址,count给出本次请求写出的字节数。const void*允许接口从这段内存取字节,不意味着接口拥有或延长了它的寿命。调用期间,调用方必须保证请求范围的内存有效。实际头文件声明还有平台自己的标注,这里只抽出正在解释的类型和参数。

std::size_t已经用于大小与下标,它是无符号计数类型;系统头文件里的size_t是对应的全局名称。ssize_t是POSIX提供的有符号整数类型,下面从<sys/types.h>取得,名称不放在std里。它让一个返回值既能携带非负的实际字节数,也能用-1表示错误;它不是“请求数的别名”。全局调用前的::读法沿用基础17.2

对于这里的write合同,成功时返回实际写出的字节数,可能少于请求值;失败时返回-1并设置errnoerrno是错误编号,必须先看到接口报告失败,再读取它;成功调用后残留的旧值不能拿来判本次失败。EINTR这个命名值表示被信号中断;旧19-a只在返回为负且错误号是它时重新尝试。其他接口的-1不一定沿用这个合同,必须查各自定义,不能推广为所有POSIX调用的统一判断。

先手算一次进度:总共准备3个字节,偏移为0,本次请求3,返回2。只承认实际完成的2,偏移变成2,剩余是1;下一次应从偏移2开始,请求剩余的1。若把偏移直接加3,就把尚未完成的最后一个字节跳过去了。

负数转成无符号数遵循整数转换的模规则。static_cast<std::size_t>(-1)会变成该无符号类型的最大值,并不会自动抛异常。因此顺序应是:先检查有符号返回值,再转换有效的非负计数。本模型仅接受正数:0被视为没有取得进展,不让循环在原地反复空转;这不表示所有接口返回0都是错误,例如read的0可表示结束。

下面的模型还拒绝返回数大于请求数的假输入,并在相加前检查max - offset,防止累计偏移溢出。加法前检查的推理沿用基础08.5。例子的有符号输入限定为-1、0、2、4,请求数为3,正值均可用size_t表示;没有把任意大整数的转换也说成安全。

观察 · 推演

手填返回量模型:请求与进展分开

requested3returned2offset0 → 201 / 04 · PIPELINErequested3returned2offset0 → 201 / 04 · PIPELINE
1 · 请求3,返回2

输入是模型手填的ssize_t值2,没有调用write。先判正,再确认不超过请求量,offset由0增加2。

1 / 4
阅读完整推演文字
  1. 1 · 请求3,返回2

    requested:3;returned:2;offset:0 → 2

    输入是模型手填的ssize_t值2,没有调用write。先判正,再确认不超过请求量,offset由0增加2。

  2. 2 · 返回-1

    returned:-1;转换:没有执行;offset:仍为2

    负值先进入拒绝分支,尚未进行size_t转换,offset保留2。这个模型没有生成或读取errno。

  3. 3 · 返回0

    returned:0;accepted:false;offset:仍为2

    模型拒绝零进展;这不意味着所有POSIX函数返回0都是错误,例如read的EOF有自己的合同。

  4. 4 · 返回4

    requested:3;returned:4,过大;offset:仍为2

    4为正但超过本次请求3,因此拒绝。失败值和不可能的进展都不能被当作已完成字节数。

// Return-value model only: no system call or actual short write occurs here.
// The supplied signed values stay within -1 through 4; requested is 3.
#include <sys/types.h>
#include <cstddef>
#include <iostream>
#include <limits>

bool advance_model(ssize_t returned, std::size_t requested, std::size_t& offset) {
    if (returned <= 0) {
        return false;
    }
    const auto count{static_cast<std::size_t>(returned)};
    if (count > requested || count > std::numeric_limits<std::size_t>::max() - offset) {
        return false;
    }
    offset += count;
    return true;
}

int main() {
    const std::size_t requested{3};
    std::size_t offset{0};
    bool accepted{advance_model(2, requested, offset)};
    std::cout << "requested=3 accepted=" << accepted << " offset=" << offset << '\n';
    accepted = advance_model(-1, requested, offset);
    std::cout << "returned=-1 accepted=" << accepted << " offset=" << offset << '\n';
    accepted = advance_model(0, requested, offset);
    std::cout << "returned=0 accepted=" << accepted << " offset=" << offset << '\n';
    accepted = advance_model(4, requested, offset);
    std::cout << "returned=4 accepted=" << accepted << " offset=" << offset << '\n';
    if (accepted || offset != 2) {
        return 1;
    }
    return 0;
}

第一次接受2,偏移0→2;接下来的-1、0、4均被拒绝,偏移保持2。后三次使用同一个偏移检查拒绝路径,是传入指定返回值的模型测试,不是真实地连续向同一三字节缓冲区发出请求。真实续写应更新剩余请求数,不能照抄测试中的固定3。

只把第一次模型输入从2改成1,预测后面每个拒绝分支留下的偏移。程序最终仍要求偏移等于2,因此这个单条件变体会返回1;它检验的是你是否注意到独立期望没有随输入修改而自动变化。若要验证新需求,应在自己的练习中重新计算期望,再明确说明这是另一组测试。

读接口还要区分返回值输出参数write从调用者的缓冲区取数据,read则把收到的数据写入调用者提供的可写缓冲区,再返回实际取得的数量。后续读程序时,要同时追踪整数结果和被写入的数据,不能只看函数返回了什么。

用四步把这个单元收住

  1. 预测状态:不运行,写出位掩码1→3→7→5、258的四字节以及返回2后的偏移;标出每一步的类型和范围。
  2. 补关键一步:自己写安全移位函数,先补拒绝条件,再补实际移位;证明拒绝32时输出参数不变。
  3. 找一个错误:解释“先把-1转成size_t再判断是否负数”为何丢失错误信号;修复顺序,而不是隐藏一个特别大的输出。
  4. 独立迁移与复述:另写一个低八位权限组合和一个小端编码例子,再说明实际POSIX接口应由哪里提供常量、返回值和有效期合同。

进入下一章前,至少能独立解释四件事:位运算与逻辑条件不同;32位操作拒绝移位量32;错误返回不能先转无符号数;保存地址不保证对象仍活着。下面两题用于检查这些理由。它们不替代完整G0,也不因读完或运行示例就自动记为掌握。

轮到你独立完成

先保存自己的预测和实现,再按需展开提示与答案。

练习1

从uint32_t的flags=3开始,依次执行flags = flags ^ 4U、flags = flags & ~2U,再计算(flags & 4U) != 0U。给出两个flags数值和最后布尔值;说明哪些位保持。

查看提示
  1. 先画低三位011;^只翻转掩码中为1的位置。
  2. & ~2U只清除值2所在位,最后用非零比较得到bool。
对照答案与推理

依次得到7、5、true。011异或100得到111;111与清位掩码相与得到101。read位1和execute位4保留,write位2清零;输出流默认会把true打印成1。

练习2

模型请求3、当前offset=0、returned为ssize_t的-1。若先把returned转为size_t,再立即加到offset,错在哪里?独立写出接受本章小整数返回值并更新偏移的判断顺序;补测2、0、4。

查看提示
  1. 不要让失败标记先失去负号。
  2. 正值还需检查不超过请求与剩余表示空间;拒绝时不修改offset。
对照答案与推理

-1转无符号size_t会得到其最大值,不再是失败标记;offset从0加它会变成巨大数。应先拒绝returned<=0,再在本章已声明正值可表示的范围内转换;检查count<=requested与count<=max-offset,全部通过才增加offset。从独立offset=0分别测试2、0、4时,应得到接受/offset2、拒绝/offset0、拒绝/offset0。这仍是给定小整数域的模型,不是任意POSIX返回类型的完整包装器。

把理解说出来

以下问题应用本页已讲授的合同;依据技能主题编写,并非公司内部真题。

解释与预测

flags=3、mask=2时,flags & mask与flags && mask分别产生什么?怎样判断某个位是否存在?

对照推理与英文回答

按位与得到整数2,逻辑与把两个非零值看成true,得到bool true。存在检查应写(flags & mask) != 0;不能用&&代替按位筛选。

Bitwise AND produces the integer two. Logical AND converts both nonzero operands to true and produces a boolean. Test the masked result against zero to check the bit.

位宽追问

本例对read_bit取反为何不是254?若图只显示低8位,需要补充什么说明?

对照推理与英文回答

在本章已检查的32位无符号操作环境中,~翻转全部32位,得到4294967294。低8位确实为11111110,但其余24位全是1,不能把图的截取宽度当作运算类型宽度。

In the checked environment, the operation flips all thirty-two bits and produces 4294967294. The low eight bits are 11111110, but the other twenty-four bits are also one. A cropped diagram does not change the operation type.

代码预测与边界

shift_left(1,31)成功后再调用shift_left(1,32),第二次的bool和output是什么?最高位置1再左移1为何另有结论?

对照推理与英文回答

第二次返回false,output保留2147483648,因为函数在移位前拒绝amount>=32。最高位的值2147483648左移1时移位量1合法,32位无符号结果丢弃移出的位而为0;不能由此推导移位量32也合法。

The second call returns false and preserves 2147483648 because it rejects the count before shifting. Shifting the top-bit value by one is a different, valid operation and produces zero in the unsigned thirty-two-bit type. Discarded high bits do not make an out-of-range shift count valid.

编码与转换

258的四字节小端编码是什么?为何解码先把uint8_t转成uint32_t再移位,且输出字节时又转为unsigned int?

对照推理与英文回答

字节数值为2、1、0、0,即十六进制02 01 00 00。小整数可能先提升为int;先扩到已确定的无符号宽类型,才能按目标宽度解释移位。字节输出转unsigned int是为了数值输出,避免uint8_t作为unsigned char时走字符输出;该编码由运算定义,不取决于主机端序。

The byte values are two, one, zero, zero. Convert each byte to the intended unsigned wide type before shifting, because small integer operands undergo promotion. Convert bytes to unsigned int for numeric output rather than character output. The encoding order is specified by the program, independently of host endianness.

找错与寿命

void-roundtrip成功读取7是否证明任意非空void*都能转为int*并读取?通过转换能否延长原对象寿命?

对照推理与英文回答

不能。例子知道地址来自同一个仍存活、对齐正确的int,并恢复原类型。非空地址本身不证明对象存在、类型匹配、大小或对齐满足要求;转换也不建立或延长对象寿命。

No. The example starts with a live, properly aligned int and restores its original pointer type. A non-null address alone proves neither object existence nor suitable type, size, or alignment. A pointer conversion does not create an object or extend its lifetime.

接口合同追问

请求3而返回2时为什么只前进2?returned=-1应何时转size_t?这个模型证明真实write短写了吗?

对照推理与英文回答

请求量是愿望,返回2才是这次报告的进展,模型只加2。-1必须在转换之前拒绝;不能先变为巨大无符号数。此程序只给ssize_t变量和参数填入已声明的小整数,没有系统调用,不能作为真实write短写或errno行为的执行证据。

The request is the desired amount; the returned two is the reported progress, so the model advances by two. Reject minus one before converting to size_t. This program uses supplied small integers and makes no system call, so it does not establish an actual short write or errno event.

同源示例下载

按正文指定条件逐个编译。接口教学模型与实际系统调用分别说明,不把模型输出当作真实I/O或设备执行。

资料与查证

  • C++20工作草案N4861:按位、提升与移位 ↗

    [expr.or]、[expr.bit.and]、[expr.xor]、[expr.unary.op]、[expr.shift]、[conv.prom]、[conv.integral]与[expr.ass];固定2020-04-01草案,核对运算类型、提升、移位边界与整数转换。

  • C++工作草案:对象指针的void转换 ↗

    [expr.static.cast]:void*恢复对象指针的对齐与原类型往返边界。此链接是持续更新草案;本章只采用C++20已有的活对象原类型往返合同。

  • POSIX:mmap与MAP_FAILED ↗

    POSIX.1-2024,SYNOPSIS、DESCRIPTION与RETURN VALUE;映射长度、权限标志、MAP_FAILED以及匿名映射。只支撑正文三态说明,不声称本章四份源码执行mmap。

  • POSIX:munmap释放映射 ↗

    POSIX.1-2024,DESCRIPTION与RETURN VALUE;成功释放与失败返回分开,释放后的访问不作为正常输出实验。

  • POSIX:ssize_t与size_t ↗

    POSIX.1-2017,DESCRIPTION:size_t是无符号整数,ssize_t是用于字节数或错误指示的有符号整数;不假定二者总是完全对应的同宽类型。

  • POSIX:write的请求、返回和中断 ↗

    POSIX.1-2017,SYNOPSIS、DESCRIPTION与RETURN VALUE;按实际写入量推进、-1与errno的合同。3请求/2返回是教学跟踪数据,不是对小pipe写入必然短写的保证。