我有一个简短的instr
,看起来像这样:
1110xxx111111111
我需要提取0-9位,我使用(instr & 0x1FF)
。然后将该数量存储在新的短期中。问题是,当发生这种情况时,它会变成0x0000000111111111
,而不是我想要的0x1111111111111111
。我怎样才能解决这个问题?谢谢!
以下是代码:
short instr = state->mem[state->pc];
unsigned int reg = instr >> 9 & 7; // 0b111
state->regs[reg] = state->pc + (instr & 0x1FF);
这是一个读取程序集的模拟器。 state
是机器,regs[]
是寄存器,pc
是mem[]
中当前指令的地址。
如果最后9位表示正数,则可以正常,但如果它们表示-1,则将其存储为全1,由我的代码解释为正值。
答案 0 :(得分:23)
假设短路为16位:
您可以手动执行此操作:(instr & 0x1FF) | ((instr & 0x100) ? 0xFE00 : 0)
。这将测试符号位(您保留的最高位,0x100
),并在符号位置位时设置其上方的所有位。您可以通过将掩码调整为0x1F
,0x10
和0xFFE0
,将低5位,第5位本身和所有位5-16分别扩展为5位。 / p>
或者你可以找一些借口将这些位分配给有符号的短片的上半部分并将它们向下移动(在此过程中获得符号扩展):short x = (instr & 0x1FF) << 7; x >>= 7;
后者可能实际上最终变得更加直截了当在集会中,不会涉及分支机构。如果instr
已签名,则可以在单个表达式中完成:(instr & 0x1FF) << 7 >> 7
。由于已经删除了高位,因此简化为instr << 7 >> 7
。将7替换为11乘以5位(16-5)。
答案 1 :(得分:12)
*无需分支*
请参阅http://graphics.stanford.edu/~seander/bithacks.html#FixedSignExtend以获取非常有用的黑客攻击列表。具体而言,扩展数字的符号非常简单:
/* generate the sign bit mask. 'b' is the extracted number of bits */
int m = 1U << (b - 1);
/* Transform a 'b' bits unsigned number 'x' into a signed number 'r' */
int r = (x ^ m) - m;
在使用上述程序之前,如果它们不为零(x = x & ((1U << b) - 1);
),您可能需要清除'x'的最高位。
如果在编译时知道位数'b'(例如,在你的情况下是5位),那么甚至有一个更简单的解决方案(如果处理器支持它并且编译器很聪明,这可能会触发特定的符号扩展指令)足够):
struct {signed int x:5;} s;
r = s.x = x;
答案 2 :(得分:4)
(instr & 0x1FF) * (1 - ((unsigned short)(instr & 0x100) >> 7))
它是如何工作的?它选择你的符号位并将其移动到2的位置。这用于生成值1(如果您的符号位不存在)或-1(如果您的符号位存在)。
此解决方案是无分支的,不依赖于未定义的行为。
答案 3 :(得分:2)
我不确定在使用0x1ff
屏蔽后你是如何获得13位的,但这应该将9位数字签名扩展为16位短数。不漂亮(或特别有效),但它的工作原理:
(instr & 0x1ff) | (0xfe00 * ((instr & 0x100) >> 8))
屏蔽符号位,移至1位置以获得0/1。将其乘以高位,如果符号为1,则9位数将与0xfe
进行“或”运算,将所有高位设置为1。
答案 4 :(得分:2)
刚刚碰到这个寻找别的东西,也许有点晚了,但也许它对其他人有用。 AFAIAC所有的C程序员都应该从编程汇编程序开始。
无论如何,扩展标志比其他2个提案更容易。只需确保使用签名变量,然后使用2班。
short instr = state->mem[state->pc];
unsigned int reg = (instr >> 9) & 7; // 0b111
instr &= 0x1ff; // get lower 9 bits
instr = ((instr << 7) >> 7); // sign extend
state->regs[reg] = state->pc + instr;
short instr = state->mem[state->pc];
unsigned int reg = (instr >> 9) & 7; // 0b111
instr &= 0x1ff; // get lower 9 bits
instr = ((instr << 7) >> 7); // sign extend
state->regs[reg] = state->pc + instr;
如果变量是有符号的,那么C编译器会翻译&gt;&gt;到算术右移保留符号。此行为与平台无关。
因此,假设instr以0x1ff开始,那么我们有,&lt;&lt; 7将SL(左移)值,因此instr现在是0xff80,然后是&gt;&gt; 7将ASR值为so,因此instr现在为0xffff。
答案 5 :(得分:1)
这更像是对先前答案的改进,但到目前为止还没有提出完全通用的解决方案。此宏将签名扩展值v
,sb
表示符号位的0位数。
#define SIGNEX(v, sb) ((v) | (((v) & (1 << (sb))) ? ~((1 << (sb))-1) : 0))
int32_t x;
SIGNEX(x, 15); // Sign bit is bit-15 (16th from the right)
SIGNEX(x, 23); // Sign bit is bit-23 (24th from the right)
它使用分支来最大化跨缺少硬件乘法或桶形移位器的平台的可移植性。
答案 6 :(得分:1)
这是一个更简单的解决方案,因为x
是 5位2 的补码,看看:
z = (x^16)-16
答案 7 :(得分:0)
不是最佳的,但可以说是易于阅读的解决方案:
要对9位值进行符号扩展,可以替换
(instr & 0x1FF)
...与...
((instr & 0x1FF) | -(instr & 0x100))
或
((instr & 0x0FF) | -(instr & 0x100))
.
在 2s 补码算法中,(instr & 0x100)
将隔离符号位,用 -(instr & 0x100)
取反将有效地对隔离的符号位进行符号扩展。最后将符号扩展的符号位与原始值(带或不带符号位)进行 OR 运算,得到符号扩展:((instr & 0x1FF) | -(instr & 0x100))
或 ((instr & 0x0FF) | -(instr & 0x100))
。