Best practice per le operazioni di spostamento circolare (rotazione) in C ++


93

Gli operatori di spostamento sinistro e destro (<< e >>) sono già disponibili in C ++. Tuttavia, non sono riuscito a scoprire come eseguire operazioni di spostamento circolare o rotazione.

Come possono essere eseguite operazioni come "Ruota a sinistra" e "Ruota a destra"?

Rotazione a destra due volte qui

Initial --> 1000 0011 0100 0010

dovrebbe risultare in:

Final   --> 1010 0000 1101 0000

Sarebbe utile un esempio.

(Nota dell'editore: molti modi comuni di esprimere le rotazioni in C soffrono di un comportamento indefinito se il conteggio delle rotazioni è zero, o si compilano per più di una singola istruzione di rotazione della macchina. La risposta di questa domanda dovrebbe documentare le migliori pratiche.)



Risposte:


104

Vedi anche una versione precedente di questa risposta su un'altra domanda a rotazione con alcuni dettagli in più su ciò che asm gcc / clang produce per x86.

Il modo più adatto al compilatore per esprimere una rotazione in C e C ++ che evita qualsiasi comportamento indefinito sembra essere l'implementazione di John Regehr . L'ho adattato per ruotare in base alla larghezza del tipo (usando tipi a larghezza fissa come 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 ));
}

Funziona per qualsiasi tipo di numero intero senza segno, non solo uint32_t, quindi puoi creare versioni per altre dimensioni.

Vedi anche una versione del modello C ++ 11 con molti controlli di sicurezza (incluso il fatto static_assertche la larghezza del tipo è una potenza di 2) , il che non è il caso di alcuni DSP a 24 bit o mainframe a 36 bit, per esempio.

Consiglierei di utilizzare il modello solo come back-end per wrapper con nomi che includono esplicitamente la larghezza di rotazione. Le regole di promozione di numeri interi significano che rotl_template(u16 & 0x11UL, 7)eseguirà una rotazione a 32 o 64 bit, non a 16 (a seconda della larghezza di unsigned long). Even uint16_t & uint16_tè promosso signed intdalle regole di promozione dei numeri interi di C ++, tranne sulle piattaforme in cui intnon è più largo di uint16_t.


Su x86 , questa versione è in linea con un singolorol r32, cl (o rol r32, imm8) con compilatori che la supportano, perché il compilatore sa che le istruzioni x86 rotate e shift mascherano lo shift-count allo stesso modo del sorgente C.

Supporto del compilatore per questo idioma che evita UB su x86, per uint32_t xe unsigned int nper turni di conteggio variabili:

  • clang: riconosciuto per le rotazioni di conteggio variabili da clang3.5, più turni + o insns prima.
  • gcc: riconosciuto per le rotazioni di conteggio variabili da gcc4.9 , più turni + o insns prima. gcc5 e versioni successive ottimizzano il ramo e la maschera anche nella versione di wikipedia, usando solo un'istruzione roro rolper i conteggi delle variabili.
  • icc: supportato per le rotazioni di conteggio variabili da ICC13 o precedenti . Il conteggio costante ruota l'uso shld edi,edi,7che è più lento e richiede più byte rispetto rol edi,7ad alcune CPU (specialmente AMD, ma anche alcune Intel), quando BMI2 non è disponibile per rorx eax,edi,25salvare un MOV.
  • MSVC: x86-64 CL19: riconosciuto solo per le rotazioni con conteggio costante. (L'idioma di wikipedia è riconosciuto, ma il ramo e AND non sono ottimizzati). Usa _rotl/ _rotrintrinsics da <intrin.h>x86 (incluso x86-64).

gcc per ARM utilizza una and r1, r1, #31per ruota variabile count, ma ancora fa ruotare il reale con una sola istruzione : ror r0, r0, r1. Quindi gcc non si rende conto che i conteggi delle rotazioni sono intrinsecamente modulari. Come dicono i documenti ARM, "ROR con lunghezza turno n, più di 32 è uguale a ROR con lunghezza turno n-32" . Penso che gcc venga confuso qui perché gli spostamenti sinistra / destra su ARM saturano il conteggio, quindi uno spostamento di 32 o più cancellerà il registro. (A differenza di x86, dove i turni mascherano il conteggio allo stesso modo delle rotazioni). Probabilmente decide di aver bisogno di un'istruzione AND prima di riconoscere l'idioma di rotazione, a causa di come funzionano gli spostamenti non circolari su quel target.

Gli attuali compilatori x86 usano ancora un'istruzione extra per mascherare un conteggio di variabili per rotazioni a 8 e 16 bit, probabilmente per lo stesso motivo per cui non evitano l'AND su ARM. Questa è un'ottimizzazione mancata, perché le prestazioni non dipendono dal conteggio delle rotazioni su qualsiasi CPU x86-64. (Il mascheramento dei conteggi è stato introdotto con 286 per motivi di prestazioni perché ha gestito i turni in modo iterativo, non con latenza costante come le CPU moderne.)

A proposito, preferisci la rotazione a destra per le rotazioni a conteggio variabile, per evitare che il compilatore 32-nesegua una rotazione a sinistra su architetture come ARM e MIPS che forniscono solo una rotazione a destra. (Questo ottimizza i conteggi delle costanti del tempo di compilazione.)

Curiosità: ARM non ha davvero spostamento dedicato / operazioni di rotazione, è solo MOV con la fonte operando passare attraverso la canna-shifter in modalità ROR : mov r0, r0, ror r1. Quindi una rotazione può trasformarsi in un operando sorgente di registro per un'istruzione EOR o qualcosa del genere.


Assicurati di utilizzare tipi senza segno per ne il valore restituito, altrimenti non sarà una rotazione . (gcc per i target x86 esegue spostamenti aritmetici a destra, spostandosi in copie del bit di segno anziché in zero, causando un problema quando si ORspostano i due valori insieme. Gli spostamenti a destra di interi con segno negativo è un comportamento definito dall'implementazione in C.)

Inoltre, assicurati che il conteggio dello spostamento sia un tipo senza segno , perché (-n)&31con un tipo con segno potrebbe essere il complemento o il segno / grandezza di uno, e non lo stesso del 2 ^ n modulare che ottieni con il complemento a due o senza segno. (Vedi i commenti sul post del blog di Regehr). unsigned intfunziona bene su ogni compilatore che ho visto, per ogni larghezza di x. Alcuni altri tipi effettivamente sconfiggono il riconoscimento del linguaggio per alcuni compilatori, quindi non usare solo lo stesso tipo di x.


Alcuni compilatori forniscono elementi intrinseci per le rotazioni , che è di gran lunga migliore di inline-asm se la versione portatile non genera un buon codice sul compilatore che stai prendendo di mira. Non ci sono elementi intrinseci multipiattaforma per nessun compilatore che io conosca. Queste sono alcune delle opzioni x86:

  • Documenti Intel che <immintrin.h>forniscono _rotle _rotl64intrinseci , e lo stesso per il giusto turno. MSVC richiede <intrin.h>, mentre gcc richiede <x86intrin.h>. An si #ifdefprende cura di gcc contro icc, ma clang non sembra fornirli da nessuna parte, tranne che in modalità di compatibilità MSVC con-fms-extensions -fms-compatibility -fms-compatibility-version=17.00 . E l'asm che emette per loro fa schifo (mascheramento extra e un CMOV).
  • MSVC: _rotr8e_rotr16 .
  • gcc e icc (non clang): <x86intrin.h>fornisce anche __rolb/ __rorbper la rotazione a sinistra / destra a 8 bit, __rolw/ __rorw(16-bit), __rold/ __rord(32-bit), __rolq/ __rorq(64-bit, definito solo per target a 64-bit). Per le rotazioni strette, l'implementazione usa __builtin_ia32_rolhio ...qi, ma le rotazioni a 32 e 64 bit sono definite usando shift / o (senza protezione contro UB, perché il codice in ia32intrin.hdeve funzionare solo su gcc per x86). GNU C sembra non avere alcuna __builtin_rotatefunzione multipiattaforma come fa per __builtin_popcount(che si espande a tutto ciò che è ottimale sulla piattaforma di destinazione, anche se non è una singola istruzione). Il più delle volte ottieni un buon codice dal riconoscimento del linguaggio.

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

Presumibilmente anche alcuni compilatori non x86 hanno elementi intrinseci, ma non espandiamo questa risposta wiki della comunità per includerli tutti. (Forse fallo nella risposta esistente sugli intrinseci ).


(La vecchia versione di questa risposta suggeriva asm inline specifico per MSVC (che funziona solo per codice x86 a 32 bit), o http://www.devx.com/tips/Tip/14043 per una versione C. I commenti stanno rispondendo a questo .)

Inline asm sconfigge molte ottimizzazioni , soprattutto in stile MSVC perché forza l'archiviazione / ricarica degli input . Una rotazione inline-asm GNU C scritta con cura consentirebbe al conteggio di essere un operando immediato per i conteggi di spostamento costanti del tempo di compilazione, ma non potrebbe comunque essere ottimizzato completamente se il valore da spostare è anche una costante del tempo di compilazione dopo l'inlining. https://gcc.gnu.org/wiki/DontUseInlineAsm .


1
Curioso, perché no bits = CHAR_BIT * sizeof(n);e c &= bits - 1;e return ((n >> c) | (n << (bits - c))), quale è quello che userei?
mirabilos

@mirabilos: la tua versione ha UB con bit = 32, count = 32, nello spostamento di bits - c= 32 - 0. (Non ho ricevuto un ping da questo perché ho solo modificato il wiki, non lo ho scritto in primo luogo.)
Peter Cordes

@PeterCordes 0 < count < bitsè un requisito costante di quasi tutte le CPU e i linguaggi di programmazione che implementano la rotazione (a volte 0 ≤ count < bits, ma lo spostamento della quantità esatta di bit è praticamente sempre non definito o arrotonda per difetto a un nop invece di cancellare il valore e ruotare, beh.)
mirabilos

@mirabilos: Esatto, ma il nostro obiettivo è scrivere una funzione che alimenti il ​​conteggio degli spostamenti direttamente a una singola istruzione asm, ma eviti UB a livello C per ogni possibile conteggio degli spostamenti. Poiché C non ha un operatore o una funzione di rotazione, vogliamo evitare UB in nessuna delle parti componenti di questo idioma. Preferiamo non fare affidamento sul fatto che il compilatore tratti uno shift in C allo stesso modo delle istruzioni asm shift sulla destinazione per cui lo compila. (E a proposito, ARM azzera il registro con spostamenti di conteggio variabili di oltre la larghezza del registro, prendendo il conteggio dal byte inferiore del registro. Link nella risposta.)
Peter Cordes

1
Stavo per dire "usa solo frammenti portatili" ma poi ho controllato il codice e sembra (a) invocare UB per conteggi a zero turni e (b) usare solo elementi intrinseci su MSVC . In generale, anche se averlo come "codice di riferimento" compilabile per ciò che funziona con tutti gli hack specifici del compilatore e della piattaforma sembra una bella idea ...
BeeOnRope

33

Poiché è C ++, usa una funzione inline:

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

Variante C ++ 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
Attenzione: questo codice è rotto se INTè un numero intero con segno e il segno è impostato! Prova ad esempio rol<std::int32_t>(1 << 31)che dovrebbe passare a 1 ma in realtà lo diventa -1(perché il segno viene mantenuto).
Nessuno il

9
@ Nessuno: ho già commentato 5 anni fa che non dovresti usare i tipi interi con segno. La rotazione non ha comunque senso sui tipi interi con segno.
MSalters

2
Puoi usare std::numeric_limits<INT>::digitsinvece di CHAR_BIT * sizeof. Dimentico se ai tipi senza segno è consentito avere un riempimento inutilizzato (ad esempio interi a 24 bit memorizzati in 32 bit), ma in tal caso digitssarebbe meglio. Vedi anche gist.github.com/pabigot/7550454 per una versione con più controllo per un turno di conteggio variabile.
Peter Cordes

1
@ PeterCordes: Lo sono. Penso che lo abbia fatto Cray (utilizzava registri a virgola mobile con riempimento dove sarebbe stato il campo esponente).
MSalters

1
@ fake-name '> quindi la versione C ++ 11 non funzionerà su Windows a meno che non la cambi in qualcos'altro ...' Sì, cambiala in linux. :)
Slava

20

La maggior parte dei compilatori ha elementi intrinseci per questo. Visual Studio ad esempio _rotr8, _rotr16


Wow! molto più facile quindi la risposta accettata. btw, per un DWORD (32 bit) usa _rotr e _rotl.
Gabe Halsmer

15

Definitivamente:

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

6
È 8un errore di ortografia di CHAR_BIT(che non deve essere esattamente 8)?
Toby Speight

2
Poiché questa è la stessa risposta della mia (tranne che scambiare destra con sinistra), il commento di Peter Cordes sulla mia risposta si applica anche qui: usa std::numeric_limits<T>::digits.
MSalters

14

C ++ 20 std::rotlestd::rotr

È arrivato! http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0553r4.html e dovrebbe aggiungerlo all'intestazione <bit>.

cppreference dice che l'utilizzo sarà come:

#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';
}

dando output:

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

Lo proverò quando il supporto arriverà a GCC, GCC 9.1.0 con g++-9 -std=c++2aancora non lo supporta.

La proposta dice:

Intestazione:

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;

e:

25.5.5 Rotazione [bitops.rot]

Nelle seguenti descrizioni, sia n std::numeric_limits<T>::digits.

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

Vincoli: T è un tipo intero senza segno (3.9.1 [basic.fundamental]).

Sia r s% N.

Restituisce: Se r è 0, x; se r è positivo (x << r) | (x >> (N - r)),; se r è negativo, rotr(x, -r).

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

Vincoli: T è un tipo intero senza segno (3.9.1 [basic.fundamental]). Sia r s% N.

Restituisce: Se r è 0, x; se r è positivo (x >> r) | (x << (N - r)),; se r è negativo, rotl(x, -r).

È std::popcountstato inoltre aggiunto A per contare il numero di 1 bit: Come contare il numero di bit impostati in un numero intero a 32 bit?


Come mai le rotazioni di bit hanno impiegato così tanto tempo per atterrare nel moderno c ++? Anche nel clang LLVM, c'erano solo elementi intrinseci solo pochi anni fa => reviews.llvm.org/D21457 Pensavo che ARM avesse ruotato molto prima del 2010, quindi avrebbero dovuto essere lì da c ++ 11.
Sandthorn

7

Che ne dici di qualcosa del genere, usando il bitset standard ...

#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,


È necessario modificarlo per tenere conto di spostamenti maggiori della lunghezza del bitset.
H. Green

Aggiunto m %= N;per tenere conto dei turni >= N.
Milania

7

Se x è un valore a 8 bit, puoi usare questo:

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

2
Probabilmente si comporterà male se xè firmato.
sam hocevar

6

In dettaglio puoi applicare la seguente logica.

Se la sequenza di bit è 33602 in numero intero

1000 0011 0100 0010

ed è necessario eseguire il Roll over con 2 shif a destra, quindi: prima fare una copia del modello di bit e poi spostarlo a sinistra: Length - RightShift cioè la lunghezza è 16 il valore di spostamento a destra è 2 16-2 = 14

Dopo 14 volte a sinistra cambiando si ottiene.

1000 0000 0000 0000

Ora sposta a destra il valore 33602, 2 volte quanto richiesto. Ottieni

0010 0000 1101 0000

Ora prendi un OR compreso tra 14 volte il valore spostato a sinistra e 2 volte il valore spostato a destra.

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

E ottieni il tuo valore di rollover spostato. Ricorda che le operazioni bit-wise sono più veloci e questo non richiede nemmeno alcun ciclo.


1
Simile alle subroutine sopra ... b = b << m | b >> (Nm);
SM Kamran,

Non dovrebbe essere XOR, non OR? 1 ^ 0 = 1, 0 ^ 0 = 0, ecc. Se OR non è esclusivo, sarà sempre 1.
BK

5

Supponendo che si desideri spostare a destra di Lbit e l'ingresso xè un numero con Nbit:

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

3

La risposta corretta è la seguente:

#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 ) ) ) )

Probabilmente si comporterà male se valè firmato.
sam hocevar

0

Codice sorgente x numero di bit

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

un altro suggerimento

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

Di seguito è riportata una versione leggermente migliorata della risposta di Dídac Pérez , con entrambe le direzioni implementate, insieme a una demo degli usi di queste funzioni che utilizzano caratteri senza segno e valori lunghi senza segno. Diverse note:

  1. Le funzioni sono inline per le ottimizzazioni del compilatore
  2. Ho usato un cout << +valuetrucco per emettere numericamente un carattere non firmato che ho trovato qui: https://stackoverflow.com/a/28414758/1599699
  3. Consiglio di utilizzare la <put the type here>sintassi esplicita per chiarezza e sicurezza.
  4. Ho usato un carattere senza segno per il parametro shiftNum a causa di quello che ho trovato nella sezione Dettagli aggiuntivi qui :

Il risultato di un'operazione di spostamento non è definito se l' espressione-additiva è negativa o se l' espressione-additiva è maggiore o uguale al numero di bit nell'espressione-spostamento (promossa) .

Ecco il codice che sto usando:

#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

Sovraccaricare una funzione:

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) )

dovresti racchiudere x tra parentesi per evitare brutte sorprese con le espressioni come argomento della macro.
Joey

3
Se il valore non è a 16 bit, ottieni silenziosamente una sciocchezza
James Hopkin,

Se lo si definisce come macro, è necessario anche fare attenzione a evitare di passare un'espressione con effetti collaterali come argomento.
Phil Miller
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.