x86-64机器码,44字节
(相同的机器代码也适用于32位模式。)
@Daniel Schepler的答案是一个起点,但这至少具有一个新的算法思想(不仅仅是更好地打高尔夫),'B'
(1000010
)和'X'
(1011000
)的ASCII码在被掩盖后为16和2。0b0010010
。
因此,在排除十进制(非零前导数字)和八进制(char after '0'
小于'B'
)之后,我们可以将base =c & 0b0010010
并跳入数字循环。
可以在x86-64 System V中作为调用,使用来unsigned __int128 parse_cxx14_int(int dummy, const char*rsi);
从unsigned __int128
结果的上半部分提取EDX返回值tmp>>64
。
.globl parse_cxx14_int
## Input: pointer to 0-terminated string in RSI
## output: integer in EDX
## clobbers: RAX, RCX (base), RSI (points to terminator on return)
parse_cxx14_int:
xor %eax,%eax # initialize high bits of digit reader
cdq # also initialize result accumulator edx to 0
lea 10(%rax), %ecx # base 10 default
lodsb # fetch first character
cmp $'0', %al
jne .Lentry2
# leading zero. Legal 2nd characters are b/B (base 2), x/X (base 16)
# Or NUL terminator = 0 in base 10
# or any digit or ' separator (octal). These have ASCII codes below the alphabetic ranges
lodsb
mov $8, %cl # after '0' have either digit, apostrophe, or terminator,
cmp $'B', %al # or 'b'/'B' or 'x'/'X' (set a new base)
jb .Lentry2 # enter the parse loop with base=8 and an already-loaded character
# else hex or binary. The bit patterns for those letters are very convenient
and $0b0010010, %al # b/B -> 2, x/X -> 16
xchg %eax, %ecx
jmp .Lentry
.Lprocessdigit:
sub $'0' & (~32), %al
jb .Lentry # chars below '0' are treated as a separator, including '
cmp $10, %al
jb .Lnum
add $('0'&~32) - 'A' + 10, %al # digit value = c-'A' + 10. we have al = c - '0'&~32.
# c = al + '0'&~32. val = m+'0'&~32 - 'A' + 10
.Lnum:
imul %ecx, %edx
add %eax, %edx # accum = accum * base + newdigit
.Lentry:
lodsb # fetch next character
.Lentry2:
and $~32, %al # uppercase letters (and as side effect,
# digits are translated to N+16)
jnz .Lprocessdigit # space also counts as a terminator
.Lend:
ret
与Daniel的版本相比,已更改的块的缩进程度(比其他指令少)。主循环的底部也有其条件分支。事实证明,这是中立的变更,因为任何一条路径都无法落入其顶端,dec ecx / loop .Lentry
进入循环想法在以不同的方式处理八进制之后并不是赢家。但是它在循环内部的指令较少,而惯用形式的循环在结构中是do {} while结构,因此我保留了它。
Daniel的C ++测试工具使用此代码在64位模式下无法更改,该代码使用与他的32位答案相同的调用约定。
g++ -Og parse-cxx14.cpp parse-cxx14.s &&
./a.out < tests | diff -u -w - tests.good
反汇编,包括作为实际答案的机器代码字节
0000000000000000 <parse_cxx14_int>:
0: 31 c0 xor %eax,%eax
2: 99 cltd
3: 8d 48 0a lea 0xa(%rax),%ecx
6: ac lods %ds:(%rsi),%al
7: 3c 30 cmp $0x30,%al
9: 75 1c jne 27 <parse_cxx14_int+0x27>
b: ac lods %ds:(%rsi),%al
c: b1 08 mov $0x8,%cl
e: 3c 42 cmp $0x42,%al
10: 72 15 jb 27 <parse_cxx14_int+0x27>
12: 24 12 and $0x12,%al
14: 91 xchg %eax,%ecx
15: eb 0f jmp 26 <parse_cxx14_int+0x26>
17: 2c 10 sub $0x10,%al
19: 72 0b jb 26 <parse_cxx14_int+0x26>
1b: 3c 0a cmp $0xa,%al
1d: 72 02 jb 21 <parse_cxx14_int+0x21>
1f: 04 d9 add $0xd9,%al
21: 0f af d1 imul %ecx,%edx
24: 01 c2 add %eax,%edx
26: ac lods %ds:(%rsi),%al
27: 24 df and $0xdf,%al
29: 75 ec jne 17 <parse_cxx14_int+0x17>
2b: c3 retq
Daniel版本的其他更改包括sub $16, %al
通过使用more sub
代替test
检测分隔符的一部分),以及数字与字母字符。
与丹尼尔不同,下面的每个字符都'0'
被视为分隔符,而不仅仅是'\''
。(除' '
:and $~32, %al
/ jnz
在我们的两个循环中,/ 都将空格视为终止符,这对于在行首使用整数进行测试可能很方便。)
%al
在循环内部进行修改的每个操作都有一个使用结果设置的分支消耗标志,并且每个分支都(或经过)到不同的位置。