Asm gcc / clang'ın x86 için ne ürettiği hakkında daha fazla ayrıntı içeren başka bir döndürme sorusundaki bu cevabın önceki bir versiyonuna da bakın .
Tanımlanmamış Davranışlardan kaçınan bir döndürmeyi C ve C ++ 'da ifade etmenin en derleyici dostu yolu, John Regehr'in uygulaması gibi görünüyor . Türün genişliğine göre döndürmek için uyarladım (sabit genişlikli türler gibi uint32_t
).
#include <stdint.h> // for uint32_t
#include <limits.h> // for CHAR_BIT
#include <assert.h>
static inline uint32_t rotl32 (uint32_t n, unsigned int c)
{
const unsigned int mask = (CHAR_BIT*sizeof(n) - 1);
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);
c &= mask;
return (n>>c) | (n<<( (-c)&mask ));
}
Herhangi bir işaretsiz tamsayı türü için çalışır, yalnızca uint32_t
başka boyutlar için sürümler oluşturabilirsiniz.
static_assert
Örneğin, bazı 24-bit DSP'lerde veya 36-bit mainframe'lerde durum böyle olmayan (tip genişliğinin 2'nin kuvveti olduğu da dahil olmak üzere) çok sayıda güvenlik denetimi içeren bir C ++ 11 şablon sürümüne bakın .
Döndürme genişliğini açıkça içeren adlara sahip sarmalayıcılar için şablonu yalnızca arka uç olarak kullanmanızı öneririm. Tamsayı yükseltme kuralları rotl_template(u16 & 0x11UL, 7)
, bunun 16 değil (genişliğine bağlı olarak unsigned long
) 32 veya 64 bit döndürme yapacağı anlamına gelir . Hatta uint16_t & uint16_t
terfi edilir signed int
platformlarda haricinde C ++ 'ın tam sayı-tanıtım kurallarına göre int
daha geniştir uint16_t
.
X86 üzerinde , bu sürüm tek için inlinesrol r32, cl
(veya rol r32, imm8
derleyici olduğunu bildiği için, onu grok derleyiciler ile) x86 döndür ve kaydırma komutları maske C kaynak yok aynı şekilde kayması-sayar.
Değişken sayılı vardiyalar için uint32_t x
ve x86'da bu UB'den kaçınan deyim için derleyici desteği unsigned int n
:
- clang: clang3.5'ten bu yana değişken sayımlı dönüşler için tanınır, bundan önce çoklu + veya insns kaydırır.
- gcc: gcc4.9'dan beri değişken sayı dönüşümleri için tanınır , bundan önce birden çok + veya insns vardiya. gcc5 ve daha sonra, wikipedia sürümündeki dalı ve maskeyi, değişken sayılar için yalnızca bir
ror
veya rol
komutunu kullanarak optimize eder .
- icc: ICC13 veya daha önceki sürümlerden bu yana değişken sayımlı dönüşümler için desteklenir . Sabit-sayma , bir MOV kaydetmek için BMI2 mevcut olmadığında, bazı CPU'lardan (özellikle AMD, ancak bazı Intel)
shld edi,edi,7
daha yavaş ve daha fazla bayt alan kullanımı döndürür .rol edi,7
rorx eax,edi,25
- MSVC: x86-64 CL19: Yalnızca sabit sayı dönüşümleri için tanınır. (Wikipedia deyimi tanınır, ancak dal ve AND optimize edilmemiştir). X86'daki
_rotl
/ _rotr
intrinsics'i kullanın <intrin.h>
(x86-64 dahil).
ARM için gcc bir kullanır and r1, r1, #31
değişken sayımı döndükçe için, ama yine de tek bir komutla gerçek döndürme yapar : ror r0, r0, r1
. Dolayısıyla gcc, rotate-count'ların doğası gereği modüler olduğunun farkında değildir. ARM belgelerinin dediği gibi, "vardiya uzunluğuna sahip ROR n
, 32'den fazlası vardiya uzunluğundaki ROR ile aynıdır n-32
" . Sanırım burada gcc'nin kafası karışıyor çünkü ARM'deki sola / sağa kaymalar sayımı doyuruyor, bu nedenle 32 veya daha fazla bir kayma kaydı temizleyecektir. (X86'nın aksine, kaydırmalar, sayıyı döndürmelerle aynı şekilde maskelemektedir). Döndürme deyimini tanımadan önce muhtemelen bir AND komutuna ihtiyacı olduğuna karar verir, çünkü bu hedefte dairesel olmayan kaydırmalar nasıl çalışır.
Mevcut x86 derleyicileri, muhtemelen AND on ARM'den kaçınmadıklarından dolayı, 8 ve 16-bit dönüşler için değişken bir sayımı maskelemek için hala ekstra bir talimat kullanıyorlar. Bu, gözden kaçan bir optimizasyondur, çünkü performans herhangi bir x86-64 CPU'daki döndürme sayısına bağlı değildir. (Sayımların maskelenmesi, modern CPU'lar gibi sabit gecikmeyle değil, vardiyaları yinelemeli olarak ele aldığı için performans nedenleriyle 286 ile tanıtıldı.)
BTW, derleyicinin 32-n
yalnızca sağa döndürme sağlayan ARM ve MIPS gibi mimarilerde bir sola döndürme uygulamasını yapmaktan kaçınmak için değişken sayımlı döndürmeler için sağa döndürmeyi tercih eder . (Bu, derleme zamanı sabiti sayımlarıyla optimize eder.)
Eğlenceli bir gerçek: ARM gerçekten adanmış bir kayma / döndürme talimatı yoktur, bu sadece MOV var kaynağına işlenen ROR modunda namlu-değiştiren geçiyor : mov r0, r0, ror r1
. Böylece bir döndürme, bir EOR komutu veya başka bir şey için bir yazmaç-kaynak işlenene katlanabilir.
İçin işaretsiz türleri n
ve dönüş değerini kullandığınızdan emin olun , aksi takdirde bu bir döndürme olmaz . (x86 hedefleri için gcc aritmetik sağa kaydırma yapar, sıfır yerine işaret bitinin kopyalarını kaydırır OR
ve iki kaydırılmış değeri birlikte yaptığınızda bir soruna yol açar . Negatif işaretli tamsayıların sağa kaydırmaları, C'de uygulama tanımlı davranıştır.
Ayrıca, vardiya sayısının işaretsiz bir tür olduğundan emin olun , çünkü (-n)&31
işaretli bir tür kişinin tümleyicisi veya işareti / büyüklüğü olabilir ve işaretsiz veya ikinin tümleyicisi ile elde ettiğiniz modüler 2 ^ n ile aynı olmayabilir. (Regehr'in blog gönderisindeki yorumlara bakın). unsigned int
baktığım her derleyicide, her genişliği için iyi sonuç veriyor x
. Diğer bazı türler aslında bazı derleyiciler için deyim tanımayı bozar, bu nedenle ile aynı türü kullanmayın x
.
Bazı derleyiciler döndürmeler için içsel bilgiler sağlar ; bu, taşınabilir sürüm hedeflediğiniz derleyicide iyi kod oluşturmazsa satır içi asm'den çok daha iyidir. Bildiğim herhangi bir derleyici için platformlar arası iç bilgi yok. X86 seçeneklerinden bazıları şunlardır:
- Sağ vardiyayı
<immintrin.h>
sağlayan _rotl
ve _rotl64
içsel olan Intel belgeleri . MSVC <intrin.h>
, gcc gerektirirken <x86intrin.h>
. An #ifdef
gcc ve icc ile ilgilenir, ancak clang bunları MSVC uyumluluk modu dışında-fms-extensions -fms-compatibility -fms-compatibility-version=17.00
hiçbir yerde sağlamıyor gibi görünmektedir . Ve onlar için yaydığı asm berbattır (ekstra maskeleme ve bir CMOV).
- MSVC:
_rotr8
ve_rotr16
.
- gcc ve icc (çınlama değil):
<x86intrin.h>
Ayrıca sağlar __rolb
/ __rorb
8 bit döndürme / sağ, sol için __rolw
/ __rorw
(16-bit), __rold
/ __rord
(32-bit), __rolq
/ __rorq
(64-bit, sadece 64-bit hedefler için tanımlanan). Dar döndürmeler için, uygulama __builtin_ia32_rolhi
veya kullanır ...qi
, ancak 32 ve 64 bit döndürmeler, shift / veya kullanılarak tanımlanır (UB'ye karşı koruma olmadan, çünkü içindeki kod ia32intrin.h
yalnızca x86 için gcc üzerinde çalışmak zorundadır). GNU C'nin olduğu gibi herhangi bir çapraz platform __builtin_rotate
işlevine sahip olmadığı görülmektedir __builtin_popcount
(bu, hedef platformda tek bir talimat olmasa bile en uygun olana genişler). Çoğu zaman deyim tanımadan iyi bir kod alırsınız.
#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 _rotl(x, n);
}
#endif
Muhtemelen bazı x86 olmayan derleyiciler de içsel özelliklere sahiptir, ancak bu topluluk-wiki yanıtını hepsini içerecek şekilde genişletmeyelim. (Belki bunu içsel konularla ilgili mevcut yanıtta yapın ).
(Bu cevabın eski sürümü, MSVC'ye özgü satır içi asm (yalnızca 32bit x86 kodu için çalışır) veya bir C sürümü için http://www.devx.com/tips/Tip/14043 önerdi . Yorumlar buna yanıt veriyor .)
Satır içi asm , girdileri depolanmaya / yeniden yüklenmeye zorladığı için özellikle MSVC tarzı birçok optimizasyonu yener . Dikkatlice yazılmış bir GNU C satır içi asm döndürme, sayımın derleme zamanı sabiti kaydırma sayıları için anında işlenen olmasına izin verir, ancak kaydırılacak değer aynı zamanda bir derleme zamanı sabiti ise, yine de tamamen optimize edemez. satır içi yaptıktan sonra. https://gcc.gnu.org/wiki/DontUseInlineAsm .