2

我正在开发一个用户输入他的名字的程序,该程序应该将所有小写字母转换为大写:

我正在使用 %s 格式来读取字符串:

.text
 ldr r0,=msj
 bl printf
 ldr r0,=format
 ldr r1,string
 bl scanf



.data
.align 2
msj: .asciz "Enter you name:  "
format: .asciz "%s"
string: .asciz ""

我尝试将每个字符减去 32,但我认为字符串不是 ascii 数字格式。

有什么办法可以将整个单词转换为大写?

4

3 回答 3

3

这可能会奏效。目前我没有任何 ARM 材料。

; call with address of string in 'R0'.
upperString:
1: ldrb r1,[r0],#1
   tst  r1      ; finished string with null terminator?
   bxeq lr      ; then done and return
   cmp  r1,#'a' ; less than a?
   blo  1b      ; then load next char.
   cmp  r1,#'z' ; greater than z?
   bhi  1b      ; then load next char.

   ; Value to upper case.
   sub  r1,r1,#('a' - 'A') ; subtract 32.
   strb r1,[r0,#-1] ; put it back to memory.
   b    1b      ; next character.

至少这是一个很好的起点。这就像wallyk 的代码,除了我假设一个以null 结尾的字符串而不是一个帕斯卡类型的字符串。称呼它,

   ldr r0,=string
   bl  upperString

变体

以上是根据伪操作的“C”格式的“NULL”(零值)终止的ASCII字符串。.asciz字符串编码的另一种格式是 Pascal 类型。Pascal 字符串是比喻性int size; char data[size]的,没有空终止符。对于 pascal 字符串,循环机制会有所不同,但核心(xor 0x20sub 'a' - 'A')对于 ASCII 编码是相同的。

一些字符串编码是不同的。对于固定宽度的字符串,常量会改变。一些字符串使用转义机制,每个“字形”或字母由不同数量的数据表示。在这种情况下,“步进”汇编程序会发生变化。

最后,使用您经常想知道的“C”库,这是一个数字,这是一个标点符号等。在这些情况下,可以为每个具有该字符属性的字符建立一个表。如果“大写”和“小写”的编码不是连续的范围,您也可以使用此表方法。

希望变体部分对非“剪切和粘贴”程序员有用。

于 2013-05-17T00:31:46.953 回答
1

检测一个字符是否介于两者之间'a'并且'z'只需要一对一subcmp进行范围检查。(有关详细信息,请参阅^= 32 背后的想法是什么,它将小写字母转换为大写字母,反之亦然。)

除了最初是小写字母的字符外,我们可以不修改所有字符。在 ARM 模式下,我们可以轻松地断言存储(如果条件为假,则充当 NOP)。假设 CPU 可以有效地处理这个问题,它不会弄脏没有小写字符的字符串的缓存。(@artless noise 的回答也是这样做的,在到达商店之前跳回循环的顶部。)

.syntax unified
@ call this with address of string in R0
upperString_ARM_mode:
   b     .Lentry          @ start in the middle of the loop.  Or put upperString: there instead of here.
.Lloop:                   @ do {
   sub    r2, r1, #'a'
   bic    r1, #0x20         @ clear the lower-case bit in the original
   cmp    r2, #'z'-'a'      @ set flags

   it  ls                   @ For Thumb2 compat; assembles to nothing in ARM mode 
   strbls   r1, [r0, #-1]   @ strb with LS predicate (Lower-or-Same unsigned <=)
                            @ store upcased version if (c-'a') <=(unsigned) length of alphabet
 .Lentry:
   ldrb   r1, [r0],#1       @ zero-extending byte load (with post-increment addressing)
   tst    r1, r1
   bne    .Lloop          @ }while( *p != 0 ) 

   bx    lr          @ return.  (R0 pointing at terminating 0 byte)

@@@ UNTESTED, except for checking that it assembles for both ARM and Thumb-2
@@@ Doesn't work for Thumb-1

除了以 a 开头b .Lentry,您可以将标签放在循环的中间,因此在循环中间使用startupperString调用它。bx upperString(通常函数标签位于函数的顶部,但如果不是,则任何假设将前面的代码视为不同函数的一部分的工具)。

重新安排循环以使条件分支位于底部(并且没有无条件分支)称为“循环旋转”优化;这就是为什么我们必须从中间开始。

不幸的是拇指模式cbnz只能向前跳转,所以你不能将它用作循环分支。


这个版本的函数在循环中的指令比@artless noise 少(7 对 10),但它们每次都运行。这对于分支预测很有用,但在不太依赖它的简单低端 CPU 上可能会更糟。

这在 ARM 或 Thumb-2 中组装(例如,使用arm-none-eabi-gcc -c -mcpu=cortex-m3),但不适用于只有 Thumb-1 的 CPU。(例如皮质-m0)。

sub具有与源不同的目标寄存器,以及那么大的立即数,不适合单个狭窄的 16 位指令,既不是 subs 也不是 sub。寻址模式也不行strb[r0, #-1]


sub/cmp 通过 2 条指令完成工作。对于某些情况,您可以使用cmp/ cmpXX(带有一些谓词)以某种有用的方式设置标志。但是在这里,cmp r1, #'a'/cmphs r1, #'z'会使 LS 条件成立,即使r1<'a'. 因此,其中一条指令必须是rsbs反向减法,或者您需要寄存器中的一个常量,这样您就可以在不修改任何寄存器的情况下执行cmp r1, #'a'/cmphs r2, r1获得一致的标志条件。


您当然可以使用 NEON SIMD 指令更快地完成此操作,一次 8 或 16 个字节,特别是如果您知道长度而不是还必须搜索终止的 0 字节。有关x86 SSE2 版本,请参阅将 C++ 中的字符串转换为大写。

于 2021-02-20T05:07:45.260 回答
0

这是基本算法:

for (int idx = 0;  idx < len;  ++idx)
    if (str [idx] >= 'A'  &&  str [idx] <= 'Z')
         str [idx] += 'a' - 'A';

它有几个你没有的部分。逐个字符扫描字符串。检查大写字母。添加(不减去)小写/大写偏移量。

请注意,这通常不适用于 Unicode。

于 2013-05-16T23:55:11.553 回答