asm volatile中冒号含义及x86端口IO内联汇编代码纠错咨询
代码错误修正说明
原有代码的核心问题是输入输出段位置错误、汇编指令操作数顺序写反:
- x86 AT&T汇编中
outb/outw/outl的语法为out 数据, 端口,原有代码把两个操作数的顺序写反了 - port变量是只读输入参数,原有代码错误将其放到了输出操作数段(第一个冒号后),应该放到输入操作数段
- 原有
out32实现硬编码了寄存器名,虽然能编译但属于不规范写法,后续修改约束极易出错。
修正后的可正常运行代码如下:
#include <stdint.h> // 8位端口输出 void out(uint16_t port, uint8_t data) { asm volatile("outb %1, %0" :: "dN"(port), "a"(data)); } // 16位端口输出 void out(uint16_t port, uint16_t data) { asm volatile("outw %1, %0" : : "dN"(port), "a"(data)); } // 32位端口输出(替换原硬编码寄存器的错误写法) void out(uint16_t port, uint32_t data) { asm volatile("outl %1, %0" : : "dN"(port), "a"(data)); }
问题逐一解答
- 内联汇编中冒号分隔各部分的规则是什么?
GCC扩展内联汇编的固定结构为:
asm volatile(汇编指令模板 : 输出操作数列表 // 第1个冒号后:汇编代码会修改的C变量,无则留空 : 输入操作数列表 // 第2个冒号后:汇编代码只读的C变量,无则留空 : 破坏声明列表 // 第3个冒号后:汇编会修改、但没在操作数中列出的寄存器/内存标记,无则留空 );
如果前面的段为空,对应的冒号不能省略:比如没有输出操作数时,需要写::表示输出段为空,直接接输入段。
- 冒号分隔区域中的"dN"、"a"分别代表什么含义?
这些是操作数约束,用来告诉编译器要把后面绑定的C变量放到什么位置:
a约束:要求将变量放入 rax/eax/ax/al 寄存器,编译器会自动适配变量的位宽dN是两个约束的组合:d约束:要求将变量放入 rdx/edx/dx 寄存器N约束:针对x86平台优化,允许端口为0~255的立即数时,直接用立即数而非dx寄存器传参
内联汇编语句中的"%0"、"%1"指代什么?
是操作数的序号占位符,按顺序对应「输出操作数+输入操作数」列表里的所有参数,从0开始计数。比如上面的代码中输入操作数按顺序是port、data,那么%0对应port,%1对应data,编译器编译时会自动将占位符替换为对应约束的寄存器/立即数。"a"、"dN"后括号内的port、data变量的作用是什么?
用来绑定约束对应的C变量,编译器会自动把变量的值放到约束要求的位置(寄存器/立即数);如果是输出操作数,汇编执行完成后编译器还会自动把寄存器的值写回对应的C变量。"%[reg]"与"%%[reg]"的区别是什么?
- 单个
%开头的是内联汇编占位符:可以是%0/%1这类序号占位符,也可以是%[port]这类命名占位符,编译器会自动替换为对应的操作数 - 两个
%开头的是实际的x86寄存器名:因为AT&T原生汇编的寄存器名本身带%前缀,而内联汇编中单个%被占位符占用了,所以要写原生寄存器名就需要加两个%转义,告诉编译器这是寄存器名,不要当作占位符替换。
内容的提问来源于stack exchange,提问作者user135142
相关产品推荐
相关产品推荐

