Как получить доступ к массиву char и изменить строчные буквы в верхнем регистре, и наоборот

в настоящее время работает над проектом класса для структурированной компьютерной организации с использованием процессора x86. Значение, к которому я обращаюсь, составляет 1 байт char, но я не знаю, как сравнить его с прописным. Они сказали, что используют таблицу 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
}

}

Что-то помогает на этом этапе. Заранее спасибо за помощь!

изменить 1:

Спасибо за все предложения и точки ясности, отредактировал мой код, чтобы отразить изменения. Некоторая проблема с нарушением прав доступа теперь.

изменить 2 (+):

Спасибо за полезные глаза людям. Я все еще собираюсь переводить все письма сейчас.

Ответ 1

Для большей ясности я просто использую чистую сборку и предполагаю, что...

  • char_array - это 32-разрядный указатель на [ebp+8].
  • array_size - это 32-битное число с двумя дополнениями в [ebp+12].
  • Для вашей платформы (так или иначе), char кодировка - ASCII.

Вы должны иметь возможность вывести это самостоятельно в встроенную сборку. Теперь, если вы посмотрите на таблицу, которую все должны помнить, но едва ли кто-нибудь делает, вы заметите некоторые важные детали...

  • Верхние буквы A через Z отображаются в коды 0x41 через 0x5A соответственно.
  • Строчные буквы A через Z отображаются в коды 0x61 через 0x7A соответственно.
  • Все остальное не является буквой и, следовательно, не нуждается в преобразовании случаев.
  • Если вы посмотрите на двоичное представление диапазонов букв верхнего и нижнего регистра, вы заметите, что они точно такие же, за исключением того, что заглавные буквы имеют бит 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, вы закончили.

Я надеюсь, что это свело на вас свет!

Ответ 2

в ASCII 'a' - 'z' и 'A' - 'Z' эквивалентны, кроме одного бита, 0x20

ваш друг здесь XOR.

если у вас есть char (либо "A" - "Z", либо "a" - "z" ), XORing с 0x20 будет переключать регистр;

до XORing, выполнение проверки диапазона имеет смысл. (чтобы увидеть, действительно ли значение действительно является буквой). Вы можете упростить эту проверку диапазона с помощью ORing значения, которое нужно проверить с помощью 0xef, которое сделает "a" - "A" и "z" - "Z", а затем сделайте проверку диапазона только один раз (если вы сравниваете только с < 'a' и > 'Z', вы будете пропускать символы между ними ('[', ']' и т.д.)

Ответ 3

Вариации этого вопроса задают все время. Эта версия проблемы (требующая условного поведения за пределами только if(isalpha(c)) c|=0x20;)) сделала проблему достаточно сложной, чтобы не сразу было ясно, как это сделать эффективно.

Оказывается, что xor не сложно было придумать, и для преобразования этого кода в безоговорочный режим ожидания или просто требуется простое изменение от xor 0x20 до and ~0x20 или or 0x20. (Упрощение еще немного возможно.)

Вот как я сделал бы это с попыткой оптимально эффективного asm. Я даже включил версию с векторами SIMD и еще одну версию байтового цикла, используя идею отладки, которую я получил от векторизации.

Чтение этого ответа, вероятно, полезно только после того, как вы поймете основные принципы, связанные с его решением, с не оптимизированным кодом. OTOH, на самом деле очень мало операций, поэтому нет большого количества кода для поиска. И я очень прокомментировал это. Есть много полезных ссылок в x86 wiki, начиная с учебников и заканчивая справочными руководствами по настройке производительности.


Преобразование между нижним и верхним регистром алфавитных символов ASCII требует только установки или очистки бита 0x20, поскольку набор символов ASCII выложен диапазонами 32 друг от друга и не пересекает границу mod32.

Для каждого байта:

  • сделать копию и безоговорочно ИЛИ с помощью 0x20
  • проверьте, не существует ли между 'a' и 'z'
  • если это так, переверните буквенный бит ASCII с использованием xor и сохраните результат обратно в массив.

Выполнение теста ASCII isalpha(3) таким образом безопасно: только исходные байты, которые попадают в диапазон 'a'.. 'z', от установки этого бита - это буквенные символы в верхнем регистре. Это просто математика, которая работает для любых двух диапазонов равного размера, которые не пересекают границу %32. (Или a %64, если соответствующий бит был 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:
        mov   al, [esi];     // load the current character
        mov   dl, al;

        // 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
    }
}

Этот код может быть более читаемым, если часть материала "design doc" находится в блоке вне кода. Это сильно забивает вещи, и похоже, что там много кода, но на самом деле очень мало инструкций. (Их просто сложно объяснить короткими комментариями. Комментирующий код сложный: комментарии, которые слишком очевидны, просто беспорядочны и требуют времени от чтения кода и полезных комментариев.)


Векторизованное

На самом деле для x86 я бы использовал SSE или AVX для выполнения 16B за один раз, выполняя тот же алгоритм, но делая сравнения с двумя pcmpgtb. И, конечно, безоговорочно сохраняя результаты, поэтому массив всех неалфавитных символов по-прежнему будет загрязнен в кеше, используя большую пропускную способность памяти.

Нет никакого сопоставления SSE без знака, но мы можем по-прежнему изменять диапазон, который мы ищем вниз. Нет значений меньше -128, поэтому в подписанном сравнении он работает так, как 0 делает в беззнаковом сравнении.

Чтобы сделать это, вычтите 128. (или добавить, или xor (безличный add), где некуда/несут/нет). Это можно сделать в той же операции, что и вычитание 'a'.

Затем используйте результат сравнения как маску для обнуления байтов в векторе 0x20, поэтому только буквенные символы получают XORed с 0x20. (0 - единичный элемент для XOR/add/sub, что часто очень удобно для условных обозначений SIMD).

См. также версию strtoupper, которая была протестирована, и код для вызова в цикле, включая обработку не- несколько-16 входов, на строках C с неявной длиной (поиск по завершению 0 на лету).

#include <immintrin.h>

// Call this function in a loop, with scalar cleanup.  (Not implemented, since it 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 nowhere for the carry to go, so it 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 identity value is 0, same as for addition
}

Этот компилируется в хороший код, даже без AVX, только с одним дополнительным movdqa, чтобы сохранить копию регистра. См. Ссылку godbolt для двух более ранних версий (один из которых использует два сравнения, чтобы сохранить их простыми, а другой - с помощью pblendvb, прежде чем я вспомнил о том, чтобы замаскировать вектор 0x20 вместо результата.)

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:
        mov   al, [esi];     // load the current character
        mov   dl, al;

        // 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 inline asm не позволяет имена .label локальных символов. Я изменил их для первой версии (с условной ветвью), но не с этим.

Использование movzx eax, byte [esi] может быть немного лучше на некоторых процессорах, чтобы избежать ложной зависимости от значения eax при вводе функции. OTOH, только AMD имеет эту проблему (и Silvermont), но movzx не так дешев, как нагрузка на AMD. (Это на Intel, один uop, который использует только порт загрузки, а не порт ALU). Работает на al после того, как это все еще хорошо, поскольку оно позволяет избежать частичного регистрационного стенда (или дополнительных инструкций, чтобы избежать его) от чтения eax после setcc записывает al. (Нет setcc r/m32, только r/m8, к сожалению).


Мне нужно задаться вопросом, что думал бы профессор, если бы кто-то передал код, подобный этому, для такого задания.: P Я сомневаюсь, что даже интеллектуальный компилятор будет использовать трюк setcc/shift, если вы не приведете к нему компилятор. (Может быть, unsigned mask = (tmp>='a' && tmp<='z'); mask <<= 5; a[i] ^= mask; или что-то в этом роде.) Компиляторы знают о трюке без знака, но gcc не использует его в некоторых случаях для компиляции, проверки диапазона по времени, даже если это может доказать, что диапазон достаточно мал.

Ответ 4

Предоставлено @KemyLand для полезного разбиения кода сборки, я выяснил, как преобразовать верхний регистр в нижний регистр и наоборот.

void changeCase (char char_array[], int array_size ) {
     //this function is designed to change lowercase letters to uppercase, and vice-versa, from a char-array given the array and its size.
__asm{
        // BEGIN YOUR CODE HERE

    mov eax, [ebp + 8];     //move to register value parameter 1 (the array)
    mov ecx, [ebp + 12];    //likewise parameter 2 (the array size)

    START:

        or ecx, ecx;    //check if pointer is 0
        cmp ecx, 0;
        je endloop;   //go to end loop

        mov dl,byte ptr [eax];  //not sure if needed, but reassurance
        cmp dl, 0x41;   // is char an A?
        jl cont;

        cmp dl, 0x5A;   // is char a Z?
        jle convertUP;

        cmp dl, 0x61;   // is char an a?
        jl cont;

        cmp dl, 0x7A;   // is char a z?
        jle convertDOWN;

        jmp cont;


    convertUP:
        or dl, 0x20;        //Yes! Finally got it working!
        mov byte ptr [eax], dl;

        jmp cont;

    convertDOWN:
        and dl, 0xdf;       //this will work for sure.
        mov[eax], dl;

        jmp cont


    cont:
        inc eax;
        dec ecx;

        jmp START;

    endloop:
}

}

Не стесняйтесь объяснять, что я мог пропустить! Спасибо всем, что помогли мне лучше понять процессор сборки x86.

Ответ 5

В таблице ascii все буквы непрерывны:

A=0x41=01000001
a=0x61=01100001

Z=0x5A=01011010
z=0x7A=01111010

Итак, вы можете видеть, что, переключая 6-й бит, вы преобразовываете форму сверху в нижний регистр.