Найкращі практики для операцій кругового зсуву (обертання) в C ++


93

Оператори лівого та правого зсуву (<< і >>) вже доступні в C ++. Однак я не міг дізнатись, як я можу виконувати операції кругового зсуву або обертання.

Як можна виконувати такі операції, як "Повернути вліво" та "Повернути вправо"?

Тут обертається двічі вправо

Initial --> 1000 0011 0100 0010

має призвести до:

Final   --> 1010 0000 1101 0000

Приклад може бути корисним.

(Примітка редактора: Багато поширених способів вираження обертань у C страждають від невизначеної поведінки, якщо кількість обертань дорівнює нулю або компілюється більш ніж просто для однієї інструкції обертання машини. Відповідь на це запитання повинна задокументувати найкращі практики.)



Відповіді:


104

Див. Також попередню версію цієї відповіді на інше запитання про обертання з деякими деталями про те, що створює asm gcc / clang для x86.

Найбільш зручним для компілятора способом вираження обертання в C та C ++, який дозволяє уникнути невизначеної поведінки, є реалізація Джона Регера . Я адаптував його для обертання на ширину типу (використовуючи типи фіксованої ширини, як uint32_t).

#include <stdint.h>   // for uint32_t
#include <limits.h>   // for CHAR_BIT
// #define NDEBUG
#include <assert.h>

static inline uint32_t rotl32 (uint32_t n, unsigned int c)
{
  const unsigned int mask = (CHAR_BIT*sizeof(n) - 1);  // assumes width is a power of 2.

  // assert ( (c<=mask) &&"rotate by type width or more");
  c &= mask;
  return (n<<c) | (n>>( (-c)&mask ));
}

static inline uint32_t rotr32 (uint32_t n, unsigned int c)
{
  const unsigned int mask = (CHAR_BIT*sizeof(n) - 1);

  // assert ( (c<=mask) &&"rotate by type width or more");
  c &= mask;
  return (n>>c) | (n<<( (-c)&mask ));
}

Роботи для будь-якого цілого числа без знака типу, а не просто uint32_t, щоб ви могли зробити версії для інших розмірів.

Див. Також версію шаблону C ++ 11 з безліччю перевірок безпеки (включаючи, static_assertщо ширина типу дорівнює 2) , що не стосується, наприклад, деяких 24-розрядних ЦСП або 36-розрядних мейнфреймів.

Я б рекомендував використовувати шаблон лише як фонове для обгортки з іменами, які явно включають ширину обертання. Правила цілочисельного просування означають, що rotl_template(u16 & 0x11UL, 7)обертання буде здійснюватися на 32 або 64 біти, а не на 16 (залежно від ширини unsigned long). Навіть uint16_t & uint16_tпросувається до signed intправил цілочисельного просування C ++, за винятком платформ, де intне ширше ніж uint16_t.


У x86 ця версія вбудовується до одногоrol r32, cl (або rol r32, imm8) із компіляторами, які її переглядають, оскільки компілятор знає, що інструкції обертання x86 і зсуву x86 маскують підрахунок зсувів так само, як це робить джерело C.

Підтримка компілятора для цієї ідіоми, що уникає UB, на x86, для uint32_t xі unsigned int nдля змін з рахунком змінних:

  • clang: розпізнається для змінної кількості обертається з clang3.5, кілька змін + або insns до цього.
  • gcc: розпізнано для змінної кількості обертається з gcc4.9 , кілька змін + або insns до цього. gcc5 та пізніших версій оптимізують гілку та маску у версії wikipedia, використовуючи лише інструкцію rorабо rolдля вказівки на кількість змінних.
  • icc: підтримується для обертання змінної кількості, починаючи з ICC13 або раніше . Постійний підрахунок обертає використання, shld edi,edi,7яке відбувається повільніше і займає більше байтів, ніж rol edi,7на деяких центральних процесорах (особливо AMD, але також деяких Intel), коли BMI2 недоступний для rorx eax,edi,25збереження MOV.
  • MSVC: x86-64 CL19: Розпізнається лише для обертання з постійним відліком. (Ідіома Вікіпедії розпізнана, але гілка та І не оптимізовані). Використовуйте _rotl/ _rotrintrinsics з <intrin.h>x86 (включаючи x86-64).

GCC для ARM використовує and r1, r1, #31для змінного кількості обертається, але по- , як і раніше робить фактичні обертатися з однією командою : ror r0, r0, r1. Отже, gcc не усвідомлює, що відліки обертання за своєю суттю модульні. Як сказано в документах ARM, "ROR із довжиною зсуву n, більше 32 - це те саме, що ROR із довжиною зсуву n-32" . Я думаю, gcc тут плутається, тому що зсуви вліво / вправо на ARM насичують рахунок, тому зміщення на 32 або більше очистить реєстр. (На відміну від x86, де зрушення маскують відлік так само, як і обертання). Ймовірно, він вирішує, що йому потрібна інструкція І, перш ніж розпізнати ідіому обертання, через те, як некруглі зміни працюють на цю ціль.

Поточні компілятори x86 все ще використовують додаткову інструкцію, щоб замаскувати кількість змінних для 8 та 16-бітового обертання, ймовірно, з тієї ж причини, що вони не уникають І на ARM. Це пропущена оптимізація, оскільки продуктивність не залежить від кількості обертань на будь-якому процесорі x86-64. (Маскування підрахунків було введено з 286 з міркувань продуктивності, оскільки воно обробляло зміщення ітеративно, а не з постійною затримкою, як сучасні процесори.)

До речі, віддайте перевагу повороту вправо для поворотів із змінним відліком, щоб уникнути змушення компілятора 32-nреалізувати поворот ліворуч на таких архітектурах, як ARM та MIPS, які забезпечують лише поворот вправо. (Це оптимізує з урахуванням констант часу підрахунку.)

Кумедний факт: ARM не дійсно має спеціальний зрушення / ротацію інструкції, це просто MOV з джерелом операнда відбувається через ствол перевертня в режимі ROR : mov r0, r0, ror r1. Тож обертання може скластися в операнд із джерелом реєстру для інструкції EOR або чогось іншого.


Переконайтеся, що використовуєте непідписані типи для nі поверненого значення, інакше воно не буде обертатися . (gcc для цілей x86 виконує арифметичні зрушення вправо, зсуваючи копії біта знака, а не нулі, що призводить до проблеми, коли ви ORобидва значення зміщуєте разом. Правильні зсуви від’ємних знакових цілих чисел - це поведінка, визначена реалізацією в C.)

Крім того, переконайтеся, що кількість змін не є типом без підпису , оскільки (-n)&31з підписаним типом це може бути доповнення або знак / величина, а не те саме, що модульний 2 ^ n, який ви отримуєте з доповненням без підпису або двох. (Див. Коментарі до повідомлення блогу Регера). unsigned intдобре працює на кожному компіляторі, який я розглядав, для кожної ширини x. Деякі інші типи насправді перемагають розпізнавання ідіом для деяких компіляторів, тому не використовуйте просто той самий тип, що і x.


Деякі компілятори забезпечують внутрішні функції для обертання , що набагато краще, ніж inline-asm, якщо портативна версія не створює хорошого коду на компіляторі, на який ви націлюєтеся. Для будь-яких компіляторів, яких я знаю, не існує внутрішніх характеристик між платформами. Ось деякі з варіантів x86:

  • Документи Intel, що <immintrin.h>надає _rotlі _rotl64внутрішні характеристики , і те саме для зрушення вправо. MSVC вимагає <intrin.h>, тоді як gcc вимагає <x86intrin.h>. An #ifdefопікується gcc проти icc, але, здається, clang-fms-extensions -fms-compatibility -fms-compatibility-version=17.00 їх ніде не надає, крім режиму сумісності MSVC з . І промінь, який він видає для них, відстій (додаткове маскування та CMOV).
  • MSVC: _rotr8і_rotr16 .
  • gcc та icc (не clang): <x86intrin.h>також передбачає __rolb/ __rorbдля 8-бітового повороту вліво / вправо, __rolw/ __rorw(16-біт), __rold/ __rord(32-біт), __rolq/ __rorq(64-біт, визначено лише для 64-бітних цілей). Для вузьких обертів реалізація використовує __builtin_ia32_rolhiабо ...qi, але 32 та 64-розрядні повороти визначаються за допомогою shift / або (без захисту від UB, оскільки код у ia32intrin.hповинен працювати лише на gcc для x86). Здається, GNU C не має жодних крос-платформних __builtin_rotateфункцій, як це робиться __builtin_popcount(яка розширюється до оптимального на цільовій платформі, навіть якщо це не одна інструкція). Найчастіше ви отримуєте хороший код із розпізнавання ідіом.

// For real use, probably use a rotate intrinsic for MSVC, or this idiom for other compilers.  This pattern of #ifdefs may be helpful
#if defined(__x86_64__) || defined(__i386__)

#ifdef _MSC_VER
#include <intrin.h>
#else
#include <x86intrin.h>  // Not just <immintrin.h> for compilers other than icc
#endif

uint32_t rotl32_x86_intrinsic(rotwidth_t x, unsigned n) {
  //return __builtin_ia32_rorhi(x, 7);  // 16-bit rotate, GNU C
  return _rotl(x, n);  // gcc, icc, msvc.  Intel-defined.
  //return __rold(x, n);  // gcc, icc.
  // can't find anything for clang
}
#endif

Імовірно, деякі компілятори, що не належать до x86, також мають властивості, але давайте не будемо розширювати цю відповідь спільноти-wiki, щоб включити їх усіх. (Можливо, зробіть це в існуючій відповіді про внутрішні характеристики ).


(Стара версія цієї відповіді пропонувала вбудований asm для MSVC (який працює лише для 32-бітного коду x86) або http://www.devx.com/tips/Tip/14043 для версії C. Коментарі відповідають на це .)

Inline asm перемагає багато оптимізацій , особливо в стилі MSVC, оскільки змушує входи зберігатися / перезавантажуватися . Ретельно написане обертання вбудованого asm GNU C дозволило б підрахуванню бути безпосереднім операндом для підрахунку зсуву константи часу компіляції, але він все одно не міг повністю оптимізувати, якщо значення, яке потрібно зсунути, також є константою часу компіляції після вкладання. https://gcc.gnu.org/wiki/DontUseInlineAsm .


1
Цікаво, чому ні bits = CHAR_BIT * sizeof(n);та c &= bits - 1;і return ((n >> c) | (n << (bits - c))), що я б використав?
mirabilos

@mirabilos: Ваша версія має UB з бітами = 32, кол = 32, у зміну на bits - c= 32 - 0. (Я не отримав від цього жодного пінгу, оскільки я лише редагував вікі, а не писав її спочатку.)
Пітер Кордес,

@PeterCordes 0 < count < bitsє постійною вимогою майже всіх процесорів та мов програмування, що реалізують обертання (іноді 0 ≤ count < bits, але перехід на точну кількість бітів практично завжди або не визначений, або округлюється до nop замість очищення значення, і обертання, ну).
mirabilos

@mirabilos: Правильно, але наша мета - написати функцію, яка подає рахунок зсувів безпосередньо в одну інструкцію asm, але уникає UB на рівні C для будь-якого можливого підрахунку змін. Оскільки C не має оператора або функції обертання, ми хочемо уникнути UB у будь-якій із складових частин цієї ідіоми. Ми воліли б не покладатися на те, що компілятор обробляє зсув C так само, як інструкції зсуву asm для цілі, для якої компілюється. (І, до речі, ARM робить нуль реєстру зі зміщеннями змінних лічильників більше, ніж ширина реєстру, беручи відлік із нижнього байта реєстру. Посилання у відповіді.)
Пітер Кордес,

1
Я збирався сказати "просто користуйся переносними фрагментами", але потім я перевірив код, і, схоже, (а) викликає UB для нульового рахунку зсувів і (б) використовую лише власні характеристики на MSVC . Взагалі, хоча мати це як компілювальний "еталонний код" для того, що працює з усіма хакерів для компілятора та платформи, здається приємною ідеєю ...
BeeOnRope

33

Оскільки це C ++, використовуйте вбудовану функцію:

template <typename INT> 
INT rol(INT val) {
    return (val << 1) | (val >> (sizeof(INT)*CHAR_BIT-1));
}

Варіант С ++ 11:

template <typename INT> 
constexpr INT rol(INT val) {
    static_assert(std::is_unsigned<INT>::value,
                  "Rotate Left only makes sense for unsigned types");
    return (val << 1) | (val >> (sizeof(INT)*CHAR_BIT-1));
}

5
Попередження: Цей код не працює, якщо INTце ціле число з підписом і знак встановлений! Наприклад, тест, rol<std::int32_t>(1 << 31)який має перевернутись на 1, але насправді стає -1(оскільки знак зберігається).
Ніхто

9
@Nobody: Я вже 5 років тому коментував, що не слід використовувати цілі типи з підписом. Обертання все одно не має сенсу для цілочисельних типів із підписом.
MSalters

2
Ви можете використовувати std::numeric_limits<INT>::digitsзамість CHAR_BIT * sizeof. Я забуваю, якщо непідписані типи можуть мати невикористані відступи (наприклад, 24-бітові цілі числа, що зберігаються в 32 бітах), але якщо так, тоді digitsбуде краще. Дивіться також gist.github.com/pabigot/7550454 для версії з додатковою перевіркою на зміну змінного числа.
Пітер Кордес,

1
@PeterCordes: Вони є. Я думаю, що Крей зробив (використовував регістри з плаваючою комою з відступами там, де було б поле експоненти).
MSalters

1
@ fake-name '>, тому версія C ++ 11 не працюватиме у Windows, якщо ви не зміните це на щось інше ...' Так, змініть це на Linux. :)
Слава

20

Більшість компіляторів мають для цього властиві властивості. Visual Studio, наприклад _rotr8, _rotr16


Оце Так! набагато простіше, ніж прийнята відповідь. до речі, для DWORD (32-розрядної) використовуйте _rotr та _rotl.
Гейб Халсмер,

15

Остаточно:

template<class T>
T ror(T x, unsigned int moves)
{
  return (x >> moves) | (x << sizeof(T)*8 - moves);
}

6
Це 8неправильне написання CHAR_BIT(яке не повинно бути рівно 8)?
Тобі Спейт

2
Оскільки це та сама відповідь, що і моя (за винятком заміни правої на ліву), тут також застосовується коментар Пітера Кордеса щодо моєї відповіді: використання std::numeric_limits<T>::digits.
MSalters

14

C ++ 20 std::rotlтаstd::rotr

Він прибув! http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0553r4.html і додайте його до <bit>заголовка.

cppreference каже, що використання буде таким:

#include <bit>
#include <bitset>
#include <cstdint>
#include <iostream>

int main()
{
    std::uint8_t i = 0b00011101;
    std::cout << "i          = " << std::bitset<8>(i) << '\n';
    std::cout << "rotl(i,0)  = " << std::bitset<8>(std::rotl(i,0)) << '\n';
    std::cout << "rotl(i,1)  = " << std::bitset<8>(std::rotl(i,1)) << '\n';
    std::cout << "rotl(i,4)  = " << std::bitset<8>(std::rotl(i,4)) << '\n';
    std::cout << "rotl(i,9)  = " << std::bitset<8>(std::rotl(i,9)) << '\n';
    std::cout << "rotl(i,-1) = " << std::bitset<8>(std::rotl(i,-1)) << '\n';
}

даючи вихід:

i          = 00011101
rotl(i,0)  = 00011101
rotl(i,1)  = 00111010
rotl(i,4)  = 11010001
rotl(i,9)  = 00111010
rotl(i,-1) = 10001110

Я спробую, коли підтримка надійде до GCC, GCC 9.1.0 g++-9 -std=c++2aвсе ще не підтримує її.

У пропозиції сказано:

Заголовок:

namespace std {
  // 25.5.5, rotating   
  template<class T>
    [[nodiscard]] constexpr T rotl(T x, int s) noexcept;
  template<class T>
    [[nodiscard]] constexpr T rotr(T x, int s) noexcept;

і:

25.5.5 Обертання [bitops.rot]

У наступних описах нехай N позначає std::numeric_limits<T>::digits.

template<class T>
  [[nodiscard]] constexpr T rotl(T x, int s) noexcept;

Обмеження: T - цілочисельний тип без знака (3.9.1 [basic.fundamental]).

Нехай r - s% N.

Повертає: якщо r дорівнює 0, x; якщо r додатний (x << r) | (x >> (N - r)),; якщо г є негативним, rotr(x, -r).

template<class T>
  [[nodiscard]] constexpr T rotr(T x, int s) noexcept;

Обмеження: T - цілочисельний тип без знака (3.9.1 [basic.fundamental]). Нехай r - s% N.

Повертає: якщо r дорівнює 0, x; якщо r додатний (x >> r) | (x << (N - r)),; якщо г є негативним, rotl(x, -r).

Також std::popcountбуло додано A для підрахунку кількості 1 біта: Як підрахувати кількість встановлених бітів у 32-розрядному цілому числі?


Як так, обертання бітів зайняло так багато часу, щоб приземлитися в сучасному C ++? Навіть у LLVM clang просто кілька років тому існували властивості => reviews.llvm.org/D21457 Я вважав, що ARM змінилася до 2010 року, тому вони мали бути там з c ++ 11.
піщанка

7

Як приблизно щось подібне, використовуючи стандартний бітсет ...

#include <bitset> 
#include <iostream> 

template <std::size_t N> 
inline void 
rotate(std::bitset<N>& b, unsigned m) 
{ 
   b = b << m | b >> (N-m); 
} 

int main() 
{ 
   std::bitset<8> b(15); 
   std::cout << b << '\n'; 
   rotate(b, 2); 
   std::cout << b << '\n'; 

   return 0;
}

HTH,


Потрібно змінити його, щоб врахувати зміщення, більші за довжину бітового набору.
H. Green

Додано m %= N;до обліку змін >= N.
Міланія

7

Якщо x - 8-бітове значення, ви можете використовувати це:

x=(x>>1 | x<<7);

2
Можливо, буде поводитися неправильно, якщо xбуде підписано
Сем Хочевар

6

У деталях ви можете застосувати таку логіку.

Якщо бітовий шаблон має значення 33602 у цілому

1000 0011 0100 0010

і вам потрібно перекинутись за допомогою 2 правих шифрів: спочатку зробіть копію бітового шаблону, а потім зсуньте його вліво: Length - RightShift, тобто довжина 16 Значення правого зсуву 2 16 - 2 = 14

Після 14 разів переїзду ви отримуєте.

1000 0000 0000 0000

Тепер змістіть значення 33602 у 2 рази, як потрібно. Ви отримуєте

0010 0000 1101 0000

Тепер візьміть АБО між значенням, зміщеним уліво за 14 разів та значенням, зміщеним у 2 рази.

1000 0000 0000 0000
0010 0000 1101 0000
===================
1010 0000 1101 0000
===================

І ви отримаєте зміщене значення перекидання. Запам’ятайте, мудрі операції швидші, і для цього навіть не потрібен цикл.


1
Подібно до підпрограм вище ... b = b << m | b >> (Нм);
SM Kamran

Це не повинен бути XOR, а не АБО? 1 ^ 0 = 1, 0 ^ 0 = 0 і т. Д. Якщо це АБО, це не ексклюзив, отже, він завжди буде 1.
BK

5

Припускаючи, що ви хочете зрушити вправо на Lбіти, а вхідне число x- це Nбіти:

unsigned ror(unsigned x, int L, int N) 
{
    unsigned lsbs = x & ((1 << L) - 1);
    return (x >> L) | (lsbs << (N-L));
}

3

Правильна відповідь така:

#define BitsCount( val ) ( sizeof( val ) * CHAR_BIT )
#define Shift( val, steps ) ( steps % BitsCount( val ) )
#define ROL( val, steps ) ( ( val << Shift( val, steps ) ) | ( val >> ( BitsCount( val ) - Shift( val, steps ) ) ) )
#define ROR( val, steps ) ( ( val >> Shift( val, steps ) ) | ( val << ( BitsCount( val ) - Shift( val, steps ) ) ) )

Можливо, буде поводитися неправильно, якщо valбуде підписано
Сем Хочевар

0

Вихідний код x бітове число

int x =8;
data =15; //input
unsigned char tmp;
for(int i =0;i<x;i++)
{
printf("Data & 1    %d\n",data&1);
printf("Data Shifted value %d\n",data>>1^(data&1)<<(x-1));
tmp = data>>1|(data&1)<<(x-1);
data = tmp;  
}

0

ще одна пропозиція

template<class T>
inline T rotl(T x, unsigned char moves){
    unsigned char temp;
    __asm{
        mov temp, CL
        mov CL, moves
        rol x, CL
        mov CL, temp
    };
    return x;
}

0

Нижче наведено трохи вдосконалену версію відповіді Дідака Переза з реалізованими обома напрямками, а також демонстрацією використання цих функцій за допомогою беззнакового символу та беззнакових довгих довгих значень. Кілька приміток:

  1. Функції призначені для оптимізації компілятора
  2. Я використав cout << +valueтрюк для стислого виведення цифрового символу, який я знайшов тут: https://stackoverflow.com/a/28414758/1599699
  3. Я рекомендую використовувати чіткий <put the type here>синтаксис для ясності та безпеки.
  4. Я використав беззнаковий символ для параметра shiftNum через те, що я знайшов у розділі Додаткові відомості тут :

Результат операції зсуву невизначений, якщо адитивний вираз є негативним або якщо адитивний вираз більше або дорівнює кількості бітів у (висунутому) виразному зсуві .

Ось код, який я використовую:

#include <iostream>

using namespace std;

template <typename T>
inline T rotateAndCarryLeft(T rotateMe, unsigned char shiftNum)
{
    static const unsigned char TBitCount = sizeof(T) * 8U;

    return (rotateMe << shiftNum) | (rotateMe >> (TBitCount - shiftNum));
}

template <typename T>
inline T rotateAndCarryRight(T rotateMe, unsigned char shiftNum)
{
    static const unsigned char TBitCount = sizeof(T) * 8U;

    return (rotateMe >> shiftNum) | (rotateMe << (TBitCount - shiftNum));
}

void main()
{
    //00010100 == (unsigned char)20U
    //00000101 == (unsigned char)5U == rotateAndCarryLeft(20U, 6U)
    //01010000 == (unsigned char)80U == rotateAndCarryRight(20U, 6U)

    cout << "unsigned char " << 20U << " rotated left by 6 bits == " << +rotateAndCarryLeft<unsigned char>(20U, 6U) << "\n";
    cout << "unsigned char " << 20U << " rotated right by 6 bits == " << +rotateAndCarryRight<unsigned char>(20U, 6U) << "\n";

    cout << "\n";


    for (unsigned char shiftNum = 0U; shiftNum <= sizeof(unsigned char) * 8U; ++shiftNum)
    {
        cout << "unsigned char " << 21U << " rotated left by " << +shiftNum << " bit(s) == " << +rotateAndCarryLeft<unsigned char>(21U, shiftNum) << "\n";
    }

    cout << "\n";

    for (unsigned char shiftNum = 0U; shiftNum <= sizeof(unsigned char) * 8U; ++shiftNum)
    {
        cout << "unsigned char " << 21U << " rotated right by " << +shiftNum << " bit(s) == " << +rotateAndCarryRight<unsigned char>(21U, shiftNum) << "\n";
    }


    cout << "\n";

    for (unsigned char shiftNum = 0U; shiftNum <= sizeof(unsigned long long) * 8U; ++shiftNum)
    {
        cout << "unsigned long long " << 3457347ULL << " rotated left by " << +shiftNum << " bit(s) == " << rotateAndCarryLeft<unsigned long long>(3457347ULL, shiftNum) << "\n";
    }

    cout << "\n";

    for (unsigned char shiftNum = 0U; shiftNum <= sizeof(unsigned long long) * 8U; ++shiftNum)
    {
        cout << "unsigned long long " << 3457347ULL << " rotated right by " << +shiftNum << " bit(s) == " << rotateAndCarryRight<unsigned long long>(3457347ULL, shiftNum) << "\n";
    }

    cout << "\n\n";
    system("pause");
}

0
--- Substituting RLC in 8051 C for speed --- Rotate left carry
Here is an example using RLC to update a serial 8 bit DAC msb first:
                               (r=DACVAL, P1.4= SDO, P1.5= SCLK)
MOV     A, r
?1:
MOV     B, #8
RLC     A
MOV     P1.4, C
CLR     P1.5
SETB    P1.5
DJNZ    B, ?1

Here is the code in 8051 C at its fastest:
sbit ACC_7  = ACC ^ 7 ; //define this at the top to access bit 7 of ACC
ACC     =   r;
B       =   8;  
do  {
P1_4    =   ACC_7;  // this assembles into mov c, acc.7  mov P1.4, c 
ACC     <<= 1;
P1_5    =   0;
P1_5    =   1;
B       --  ; 
    } while ( B!=0 );
The keil compiler will use DJNZ when a loop is written this way.
I am cheating here by using registers ACC and B in c code.
If you cannot cheat then substitute with:
P1_4    =   ( r & 128 ) ? 1 : 0 ;
r     <<=   1;
This only takes a few extra instructions.
Also, changing B for a local var char n is the same.
Keil does rotate ACC left by ADD A, ACC which is the same as multiply 2.
It only takes one extra opcode i think.
Keeping code entirely in C keeps things simpler sometimes.

-1

Перевантаження функції:

unsigned int rotate_right(unsigned int x)
{
 return (x>>1 | (x&1?0x80000000:0))
}

unsigned short rotate_right(unsigned short x) { /* etc. */ }

-1
#define ROTATE_RIGHT(x) ( (x>>1) | (x&1?0x8000:0) )

вам слід обернути x у дужки, щоб уникнути неприємних сюрпризів з виразами як аргументом для макросу.
Джої,

3
Якщо значення не 16-бітове, ви мовчки отримуєте дурниці
Джеймс Хопкін,

Визначаючи його як макрос, тоді також потрібно бути обережним, щоб не передавати вираз із побічними ефектами як аргумент.
Філ Міллер,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.