当前位置: 首页 > 知识库问答 >
问题:

如何访问字符数组并将小写字母更改为大写字母,反之亦然

应子真
2023-03-14

我目前正在使用x86处理器为结构化计算机组织进行一个类项目。我访问的值是一个1字节的字符,但我不知道如何将其与大写字符进行比较。他们说使用十六进制格式的ASCII表,但我甚至不知道如何比较这两者。

void changeCase (char char_array[], int array_size ) {
    __asm {
            // BEGIN YOUR CODE HERE
 
        mov eax, char_array;        //eax is base image
        mov edi, 0;
        
    readArray:
        cmp edi, array_size;
        jge  exit;
        mov ebx, edi;           //using ebx as offset
        shl ebx, 2;
        mov cl, [eax + ebx];    //using ecx to be the storage register
    
    check:
        //working on it
        cmp cl, 0x41;       //check if cl is <= than ASCII value 65 (A)
        jl next_indx;
        cmp cl, 0x7A;       //check if cl is >= than ASCII value 122 (z)
        jg next_indx;
        cmp cl, 'a';
        jl convert_down;
        jge convert_up;
        

    convert_down:
        or cl, 0x20;        //make it lowercase
        jmp write;

    convert_up:
        and cl, 0x20;       //make it uppercase
        jmp write;

    write:
        mov byte ptr [eax + ebx], cl    //slight funky town issue here,

    next_indx:
        inc edi;

    exit:
        cmp edi, array_size;
        jl readArray;

    mov char_array, eax;
            // END YOUR CODE HERE
    }
}

在这一点上,任何事情都有帮助。提前感谢您的帮助!

感谢所有的建议和清晰的观点,编辑我的代码以反映变化。现在存在访问冲突问题。

感谢乐于助人的眼睛。我现在还在翻译所有信件。

共有3个答案

濮君植
2023-03-14

在ASCII码中,' a'-'z '和' A'-'Z '是等价的,除了一位,0x20

你的朋友在这里是XOR。

如果你有一个字符(“A”-“Z”或“a”-“z”),用0x20对其进行XOR将切换大小写;

在XORing之前,进行范围检查是有意义的。(查看该值是否真的是一个字母)
您可以通过将该值与0xef进行“或”运算来简化此范围检查,这将使“a”变为“a”,“z”变成“z”,然后只进行一次范围检查
(如果您只与

谢雅珺
2023-03-14

为了清楚起见,我只使用纯汇编并假设。。。

  • char_array是位于[ebp 8]的32位指针。
  • array_size是位于[ebp 12]的2位补码32位数字。
  • 对于您的平台(无论如何大多数平台都是这样),char的编码是ASCII。

您应该能够自己推导出内联汇编。现在,如果你看一下每个人都应该记得但几乎没人记得的表格,你会注意到一些重要的细节...

  • 大写字母AZ分别映射到代码0x410x5A
  • 小写字母az分别映射到代码0x610x7A
  • 其他一切都不是字母,因此不需要大小写转换。
  • 如果您查看大写字母和小写字母范围的二进制表示,您会注意到它们完全相同,唯一的例外是大写字母已清除第6位,小写字母已设置。

结果,算法将是...

while array_size != 0
    byte = *char_array
    if byte >= 0x41 and byte <= 0x5A
        *char_array |= 0x20 // Turn it lowercase
    else if byte >= 0x61 and byte <= 0x7A
        *char_array &= 0xDF // Turn it uppercase
    array_size -= 1
    char_array += 1

现在,让我们把它翻译成组装…

mov eax, [ebp+8]      # char *eax = char_array
mov ecx, [ebp+12]     # int ecx = array_size

.loop:
    or ecx, ecx       # Compare ecx against itself
    jz .end_loop      # If ecx (array_size) is zero, we're done
    mov dl, [eax]     # Otherwise, store the byte at *eax (*char_array) into `char dl`
    cmp dl, 'A'       # Compare dl (*char_array) against 'A' (lower bound of uppercase letters)
    jb .continue      # If dl` (*char_array) is lesser than `A`, continue the loop
    cmp dl, 'Z'       # Compare dl (*char_array) against 'Z' (upper bound of uppercase letters)
    jbe .is_uppercase # If dl (*char_array) is lesser or equal to 'Z', then jump to .is_uppercase
    cmp dl, 'a'       # Compare dl (*char_array) against 'a' (lower bound of lowercase letters)
    jb .continue      # If dl (*char_array) is lesser than 'a', continue the loop
    cmp dl, 'z'       # Compare dl (*char_array) against 'z' (upper bound of lowercase letters)
    jbe .is_lowercase # If dl (*char_array) is lesser or equal to 'z', then jump to .is_lowercase
    jmp .continue     # All tests failed, so continue the loop

    .is_uppercase:
        or dl, 20h    # Set the 6th bit
        mov [eax], dl # Send the byte back to where it came from
        jmp .continue # Continue the loop

    .is_lowercase:
        and dl, DFh   # Clear the 6th bit
        mov [eax], dl # Send the byte back to where it came from
        jmp .continue # Continue the loop

    .continue:
        inc eax       # Increment `eax` (`char_array`), much of like a pointer increment
        dec ecx       # Decrement `ecx` (`array_size`), so as to match the previous pointer increment
        jmp .loop     # Continue

.end_loop:

一旦代码到达.end_loop,您就完成了。

我希望这能照亮你!

孙子民
2023-03-14

这个问题的各种变体总是被问到。这个问题的版本(要求条件行为不仅仅是if(isalpha(c))c|=0x20)使问题变得足够复杂,以至于无法立即看出如何有效地解决。

事实证明,xor并不难想到,将此代码转换为无条件的上写或下写只需要从 xor 0x20~0x20 或 0x20的简单更改。(简化一点也是可能的。

这是我如何尝试最有效的am。我甚至包含了一个带有SIMD向量的版本,以及另一个使用我从矢量化中得到的无分支想法的字节循环版本。

阅读这个答案可能只有在你理解了用不那么优化的代码解决这个问题所涉及的基本原则之后才有用。OTOH,实际需要的操作很少,所以没有太多的代码可以解决。我确实对此进行了大量评论。x86 标签 wiki 中有许多有用的链接,从教程到参考指南再到性能调优。

在小写和大写字母ASCII字符之间进行转换只需要设置或清除0x20位,因为ASCII字符集的布局范围彼此为32,并且不跨越mod32边界。

对于每个字节:

  • 复制并无条件地或用0x20
  • 检查它是否在'a''z'
  • 之间
  • 如果是这样,使用xor翻转ASCII字母大小写位并将结果存储回数组中。

以这种方式进行 ASCII isalpha(3) 测试是安全的: 唯一以 “a” 结尾的源字节。“z”的范围从设置该位是大写字母字符。它只是数学,适用于任何两个大小相等的范围,这些范围不会越过2边界。(或者 d 边界例如,如果相关位0x40)。

为了更有效地进行比较,我使用了无符号比较技巧,因此循环内只有一个条件分支(循环条件本身除外)。有关说明,请参阅代码中的注释。

/******** Untested. ************/

// ASCII characters are flipped to the opposite case (upper <-> lower)
// non-ASCII characters are left unchanged
void changeCase (char char_array[], int array_size ) {

    __asm{
            // BEGIN YOUR CODE HERE

        mov   esi, char_array;      // MSVC inline asm requires these potentially-redundant copies :(
        mov   ecx, array_size;

        test  ecx,ecx;       // return if(size <= 0)
        jle  early_out;

    next_char:
        movzx eax, byte ptr [esi];     // load the current character
        mov   edx, eax;              // save a copy to maybe flip + store

        // check if the character is alphabetic or not
        // there are two equal-size ranges of characters: one with 0x20 set, and one without
        or    al, 0x20;      // set 0x20 and then just check that lowercase range

        // unsigned compare trick: 0 <= n < high  can be done with one unsigned compare instead of two signed compares
        // low < n < high  can be done by shifting the range first
        sub   al, 'a';       // if al is less than 'a', it will become a large unsigned number
        cmp   al, 'z'-'a';
        ja  non_alpha;      // conditionally skip the flip & store

        xor   dl, 0x20;      // toggle the ASCII case bit
        mov   [esi], dl;
                             // xor [esi], 0x20   // saves the mov earlier, but is otherwise slower
    non_alpha:

        inc   esi;
        dec   ecx;
        jz next_char;

    early_out:
            // END YOUR CODE HERE
    }
}

如果某些“设计文档”内容位于代码外部的块中,则此代码可能更具可读性。它把事情弄得乱七八糟,看起来好像有很多代码,但实际上指令很少。(它们只是很难用简短的评论来解释。注释代码很棘手:太明显的注释只是杂乱无章,占用了阅读代码和有用注释的时间。

实际上,对于x86,我会使用SSE或AVX一次做16B,做同样的算法,但用两个pcmpgtb进行比较。当然,无条件地存储结果,因此所有非字母字符的数组仍然会在缓存中变脏,从而使用更多的内存带宽。

没有无符号的SSE比较,但我们仍然可以将我们正在寻找的范围范围向下移动到底部。没有小于 -128 的值,因此在有符号比较中,它的工作方式与 0 在无符号比较中的工作方式相同。

为此,减去< code>128。(或加,或异或(无进位加法);无处可借)。这可以在与减去< code>'a'相同的操作中完成。

然后使用比较结果作为掩码,将向量< code>0x20中的字节清零,因此只有字母字符与0x20进行异或运算。(0是XOR/add/sub的单位元素,这对于SIMD条件来说非常方便)。

另请参阅已经过测试的 strtoupper 版本,以及在循环中调用它的代码,包括在隐式长度 C 字符串上处理 16 个输入的非倍数(动态搜索终止 0)。

#include <immintrin.h>

// Call this function in a loop, with scalar cleanup.  (Not implemented, since it's the same as any other vector loop.)

// Flip the case of all alphabetic ASCII bytes in src
__m128i inline flipcase(__m128i src) {
    // subtract 'a'+128, so the alphabetic characters range from -128 to -128+25 (-128+'z'-'a')
    // note that adding 128 and subtracting 128 are the same thing for 8bit integers.
    // There's nowhere for the carry to go, so it's just xor (carryless add), flipping the high bit

    __m128i lcase = _mm_or_si128(src, _mm_set1_epi8(0x20));

    __m128i rangeshift= _mm_sub_epi8(lcase, _mm_set1_epi8('a'+128));
    __m128i non_alpha = _mm_cmpgt_epi8(rangeshift, _mm_set1_epi8(-128 + 25));  // 0:alphabetic   -1:non-alphabetic

    __m128i flip  = _mm_andnot_si128(non_alpha, _mm_set1_epi8(0x20));       // 0x20:alpha    0:non-alpha

    return          _mm_xor_si128(src, flip);
    // just mask the XOR-mask so non-alphabetic elements are XORed with 0 instead of 0x20
    // XOR's identity value is 0, same as for addition
}

即使没有AVX,这也可以编译成很好的代码,只需要一个额外的< code>movdqa来保存寄存器的副本。在我记得屏蔽< code>0x20的向量而不是结果之前,请参阅godbolt链接以获得两个早期版本(一个使用两个比较以保持简单,另一个使用< code>pblendvb。)

flipcase:
        movdqa  xmm2, XMMWORD PTR .LC0[rip]    ; 0x20
        movdqa  xmm1, xmm0
        por     xmm1, xmm2
        psubb   xmm1, XMMWORD PTR .LC1[rip]    ; -31
        pcmpgtb xmm1, XMMWORD PTR .LC2[rip]    ; -103
        pandn   xmm1, xmm2
        pxor    xmm0, xmm1
        ret

section .rodata
    .LC0:   times 16 db  32
    .LC1:   times 16 db  -31
    .LC2:   times 16 db  -103
        mov   esi, char_array;
        mov   ecx, array_size;

        test  ecx,ecx;       // return if(size <= 0)
        jle  .early_out;

    ALIGN 16   ; really only need align 8 here, since the next 4 instructions are all 2 bytes each (because op  al, imm8  insns have a special encoding)
    .next_char:
        movzx  eax, byte ptr [esi];     // load the current character
        mov    edx, eax;

        // check if the character is alphabetic or not
        or    al, 0x20;        
        sub   al, 'a';
        cmp   al, 'z'-'a';   // unsigned compare trick: 'a' <= al <= 'z'
        setna al;            // 0:non-alpha  1:alpha  (not above)
        shl   al, 5;         // 0:non-alpha  0x20:alpha
        xor   dl, al;        // conditionally toggle the ASCII case bit
        mov   [esi], dl;     // unconditionally store

        inc   esi;
        dec   ecx;           // for AMD CPUs, or older Intel, it would be better to compare esi against an end pointer, since cmp/jz can fuse but dec can't.  This saves an add ecx, esi outside the loop
        jz .next_char;
    .early_out:

对于 64 位代码,只需使用 rsi 而不是 esi。其他一切都是一样的。

显然,MSVC 内联 asm 不允许使用 .label 本地符号名称。我为第一个版本(使用条件分支)更改了它们,但不是这个版本。

使用movzx-eax,byte[esi]mov al,[esi]好,避免了对AMD、Intel Haswell及更高版本和Silvermont系列的循环错误依赖movzx并不像旧版AMD上的负载那么便宜。(它在Intel和AMD Ryzen上至少有一个uop只使用加载端口,而不是ALU端口)。为什么GCC不使用部分寄存器?

之后在< code>al上操作仍然可以。因为在< code>setcc写< code>al之后,我们不读取< code>eax,所以没有部分寄存器停顿(或避免它的额外指令)。(没有< code>setcc r/m32,只有< code>r/m8,很遗憾)。

我想知道如果有人为了这样的作业而交这样的代码,教授会怎么想。: P我怀疑即使是聪明的编译器也不会使用setcc/shift技巧,除非你引导编译器走向它。(也许无符号掩码=(tmp

 类似资料:
  • 我正在尝试使用正则表达式将大写字母替换为相应的小写字母。因此 变成 在Sublime的文本中。如何在同时包含小写和大写字母的单词中使用小写字母?所以它会影响维纳斯,而不是维纳斯。

  • 将一个字符串的第一个字母(大写字母)变成小写。 使用数组解构和 String.toLowerCase() 将第一个字母变成小写,...rest 是获取第一个字母之后字符数组,然后使用 Array.join('') 使其再次拼接成为字符串。 省略 upperRest 参数来保持字符串的其余部分不变,或者将其设置为 true 来将字符串的其余部分转换为大写。 const decapitalize =

  • 目标是从string中获取新的字符串,该字符串没有大写前的小写。在本例中,我们应该得到输出“neyork” 我试图通过ASCII表中大小写字母的位置来实现这一点,但它不起作用。我不确定是否可以通过ASCII表中的位置,以simillar的方式来实现这一点。

  • 问题内容: 如何仅使用CSS将以下每个句子的大写字母转换为小写和首字母大写? 来自: 这是一个例句。 收件人: 这是一个例句。 更新: 当我使用文本转换时:大写;结果还是一样。 问题答案: CSS中没有句子大写选项。其他答案表明是不正确的,因为该选项将 每个单词 都大写。 如果您只希望 每个元素* 的 首字母 大写,则这是一种 粗略的 实现方式,但是与实际的句子大写绝对不符: *

  • 问题内容: 我一直在谷歌搜索很多,但没有找到我的问题的答案: 如何使用正则表达式检查字符串是否至少包含以下各项之一: 大写字母 小写字母 数字 特殊字符: !@#$%^&*()-_=+|[{]};:’“,<.>/?` 所以,我至少需要一个大写字母 和 至少一个小写字母 和 至少一个数字 和 至少一个特殊字符。 我敢肯定答案很简单,但是我找不到。任何帮助是极大的赞赏。 问题答案: 尽管我个人会使用M

  • 以句子大小写返回当前文本的字符串表示形式。句格是在句子中使用大写字母或只大写第一个词和任何专有名词的常规方式。此外,所有大写字都应保持原样。 对于这份作业,名词仅限于开头有一个大写字母的单词。