据我所知,gcc 内联汇编中使用的约束告诉 gcc 输入和输出变量必须(或必须(去哪里才能生成有效的程序集。正如《精细手册》所说,"对操作数放置的限制"。
下面是教程中的一个具体工作示例。
static inline uint8_t inb(uint16_t port)
{
uint8_t ret;
asm volatile ( "inb %1, %0"
: "=a"(ret)
: "Nd"(port) );
return ret;
}
inb
是AT&T语法,表示从I/O端口接收一个字节的i386IN
指令。
以下是此指令的规格,取自 i386 手册。请注意,端口号从0x0000
到0xFFFF
。
IN AL,imm8 // Input byte from immediate port into AL
IN AX,imm8 // Input word from immediate port into AX
IN EAX,imm8 // Input dword from immediate port into EAX
IN AL,DX // Input byte from port DX into AL
IN AX,DX // Input word from port DX into AX
IN EAX,DX // Input dword from port DX into EAX
给定像uint8_t x = inb(0x80);
这样的语句,程序集输出正确地inb $0x80,%al
.它使用了指令的IN AL,imm8
形式。
现在,假设我只关心IN AL,imm8
形式,从0x00
和0xFF
(含(之间的端口接收uint8_t
。这与工作示例之间的唯一区别是,port
现在是一个uint8_t
模板参数(以使其有效地成为常量(,并且约束现在"N"
。
template<uint8_t port>
static inline uint8_t inb()
{
uint8_t ret;
asm volatile ( "inb %1, %0"
: "=a"(ret)
: "N"(port) );
return ret;
}
失败!
我认为"N"约束意味着"对于此指令,您必须有一个常量的无符号 8 位整数",但显然并非如此,因为它是"不可能的约束"。uint8_t
模板参数不是常量的无符号 8 位整数吗?
如果我将"N"替换为"Nd",则会出现不同的错误:
./test.h: Assembler messages:
./test.h:23: Error: operand type mismatch for `in'
在这种情况下,汇编器输出inb %dl, %al
这显然是无效的。
为什么这仅适用于"Nd"
和uint16_t
,而不适用于"N"
和uint8_t
?
编辑:
这是我在 godbolt.org 上尝试过的精简版本:
#include <cstdint>
template<uint8_t N>
class Port {
public:
uint8_t in() const {
uint8_t data;
asm volatile("inb %[port], %%al"
:
: [port] "N" (N)
: // clobbers
);
return data;
}
};
void func() {
Port<0x7F>().in();
}
有趣的是,这工作正常,除非您将 N 更改为 0x80 到 0xFF 之间的任何值。在 clang 上,这将生成"128 超出约束 N 的范围"错误。这会在 gcc 中生成更通用的错误。
根据约束的文档方式,代码应按预期工作。
一年多后,这似乎仍然是一个错误。编译器似乎正在将N
从无符号值转换为有符号值,并尝试将其传递到内联程序集约束中。当传递到约束中的值无法表示为 8 位有符号值时,这当然会失败。假设输入约束"N"
允许无符号 8 位值,并且应接受 0 到 255 (0xff( 之间的任何值:
N
无符号 8 位整数常量(用于输入和输出指令(。
GCC的bugzilla有一个类似的错误报告,标题为">常量约束检查符号扩展了无符号的常量输入操作数"。
在其中一个相关线程中,建议您可以通过 ANDING (&( 0xff常量(即:N & 0xff
(来解决此问题。我还发现静态强制转换N
到比uint8_t
宽的无符号类型也有效:
#include <cstdint>
template<uint8_t N>
class Port {
public:
uint8_t in() const {
uint8_t data;
asm volatile("inb %[port], %0"
: "=a"(data)
: [port] "N" (static_cast<uint16_t>(N))
: // clobbers
);
return data;
}
};
void func() {
Port<0x7f>().in();
Port<0x80>().in();
// Port<0x100>().in(); // Fails as expected since it doesn't fit in a uint8_t
}
要测试这一点,您可以在 godbolt 上玩它。