Xem thêm phiên bản trước của câu trả lời này trên một câu hỏi xoay khác với một số chi tiết hơn về những gì asm gcc / clang sản xuất cho x86.
Cách thân thiện nhất với trình biên dịch để thể hiện sự xoay vòng trong C và C ++ mà tránh mọi Hành vi không xác định dường như là cách thực hiện của John Regehr . Tôi đã điều chỉnh nó để xoay theo chiều rộng của loại (sử dụng các loại có chiều rộng cố định như 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 ));
}
Hoạt động cho bất kỳ kiểu số nguyên không dấu nào, không chỉ uint32_t
, vì vậy bạn có thể tạo phiên bản cho các kích thước khác.
Xem thêm phiên bản mẫu C ++ 11 với nhiều kiểm tra an toàn (bao gồm kiểm tra static_assert
độ rộng kiểu là lũy thừa của 2) , ví dụ như trường hợp này không xảy ra trên một số DSP 24 bit hoặc máy tính lớn 36 bit.
Tôi khuyên bạn chỉ nên sử dụng mẫu làm back-end cho các trình bao bọc có tên bao gồm chiều rộng xoay một cách rõ ràng. Quy tắc thăng hạng số nguyên có nghĩa là rotl_template(u16 & 0x11UL, 7)
sẽ thực hiện xoay vòng 32 hoặc 64 bit, không phải 16 (tùy thuộc vào chiều rộng của unsigned long
). Thậm chí uint16_t & uint16_t
được thăng hạng signed int
bởi các quy tắc xúc tiến số nguyên của C ++, ngoại trừ trên các nền tảng int
không rộng hơn uint16_t
.
Trên x86 , phiên bản này liên kết với mộtrol r32, cl
(hoặc rol r32, imm8
) các trình biên dịch tìm kiếm nó, bởi vì trình biên dịch biết rằng các lệnh xoay và chuyển x86 che dấu số đếm theo cách giống như cách nguồn C.
Hỗ trợ trình biên dịch cho thành ngữ tránh UB này trên x86, cho uint32_t x
và unsigned int n
cho sự thay đổi số lượng biến:
- clang: được công nhận cho các vòng quay đếm biến kể từ clang3.5, nhiều ca + hoặc lót trước đó.
- gcc: được công nhận cho các vòng quay số lượng biến kể từ gcc4.9 , nhiều ca + hoặc xen kẽ trước đó. gcc5 trở lên cũng tối ưu hóa nhánh và mặt nạ trong phiên bản wikipedia, chỉ sử dụng một
ror
hoặc rol
hướng dẫn cho số lượng biến.
- icc: được hỗ trợ cho các vòng quay đếm biến kể từ ICC13 trở về trước . Việc sử dụng luân phiên đếm hằng số
shld edi,edi,7
chậm hơn và chiếm nhiều byte hơn rol edi,7
trên một số CPU (đặc biệt là AMD, nhưng cũng có thể là một số Intel), khi BMI2 không có sẵn rorx eax,edi,25
để lưu MOV.
- MSVC: x86-64 CL19: Chỉ được công nhận cho các vòng quay đếm không đổi. (Thành ngữ wikipedia được công nhận, nhưng nhánh và AND không được tối ưu hóa). Sử dụng
_rotl
/ _rotr
nội dung từ <intrin.h>
trên x86 (bao gồm cả x86-64).
gcc cho ARM sử dụng một and r1, r1, #31
cho quay biến đếm, nhưng vẫn hiện xoay thực tế với một chỉ dẫn duy nhất : ror r0, r0, r1
. Vì vậy, gcc không nhận ra rằng số lần xoay vốn là mô-đun. Như các tài liệu của ARM nói, "ROR với độ dài dịch chuyển n
, hơn 32 bằng với ROR với độ dài dịch chuyển n-32
" . Tôi nghĩ rằng gcc bị nhầm lẫn ở đây vì dịch chuyển trái / phải trên ARM bão hòa số lượng, do đó, sự thay đổi từ 32 trở lên sẽ xóa sổ đăng ký. (Không giống như x86, trong đó các ca shift che số đếm giống như số lần quay). Nó có thể quyết định rằng nó cần một lệnh AND trước khi nhận ra thành ngữ xoay, vì cách thức hoạt động của các dịch chuyển không tròn trên mục tiêu đó.
Các trình biên dịch x86 hiện tại vẫn sử dụng một lệnh bổ sung để che một số biến cho các vòng quay 8 và 16-bit, có thể vì lý do tương tự mà chúng không tránh AND trên ARM. Đây là một tối ưu hóa bị bỏ qua, vì hiệu suất không phụ thuộc vào số lần xoay trên bất kỳ CPU x86-64 nào. (Mặt nạ số đếm đã được giới thiệu với 286 vì lý do hiệu suất vì nó xử lý các thay đổi lặp đi lặp lại, không phải với độ trễ liên tục như các CPU hiện đại.)
BTW, thích xoay-phải cho các vòng quay đếm biến, để tránh làm cho trình biên dịch 32-n
thực hiện xoay trái trên các kiến trúc như ARM và MIPS chỉ cung cấp xoay-phải. (Điều này tối ưu hóa với số lượng hằng số thời gian biên dịch.)
Thực tế thú vị: ARM không thực sự có hướng dẫn thay đổi / xoay chuyên dụng, nó chỉ là MOV với toán hạng nguồn đi qua bộ chuyển dịch thùng ở chế độ ROR : mov r0, r0, ror r1
. Vì vậy, một vòng xoay có thể gấp lại thành toán hạng nguồn đăng ký cho một lệnh EOR hoặc một cái gì đó.
Đảm bảo rằng bạn sử dụng các loại không dấu cho n
và giá trị trả về, nếu không nó sẽ không phải là một vòng xoay . (gcc cho các mục tiêu x86 dịch chuyển sang phải số học, dịch chuyển theo các bản sao của bit dấu thay vì số 0, dẫn đến sự cố khi bạn OR
chuyển hai giá trị cùng nhau. Dịch sang phải của các số nguyên có dấu âm là hành vi được triển khai xác định trong C.)
Ngoài ra, hãy đảm bảo số lượng dịch chuyển là kiểu không dấu , vì (-n)&31
với kiểu có dấu có thể là phần bù hoặc dấu / độ lớn của một người và không giống với mô-đun 2 ^ n mà bạn nhận được với phần bù không dấu hoặc hai. (Xem bình luận trên bài đăng trên blog của Regehr). unsigned int
hoạt động tốt trên mọi trình biên dịch mà tôi đã xem xét, cho mọi chiều rộng của x
. Một số kiểu khác thực sự đánh bại khả năng nhận dạng thành ngữ đối với một số trình biên dịch, vì vậy đừng chỉ sử dụng cùng kiểu như x
.
Một số trình biên dịch cung cấp bản chất cho các vòng quay , điều này tốt hơn nhiều so với inline-asm nếu phiên bản di động không tạo mã tốt trên trình biên dịch mà bạn đang nhắm mục tiêu. Không có bản chất nền tảng đa nền tảng nào cho bất kỳ trình biên dịch nào mà tôi biết. Đây là một số tùy chọn x86:
- Các tài liệu Intel
<immintrin.h>
cung cấp _rotl
và _rotl64
nội dung , và tương tự cho sự thay đổi đúng đắn. MSVC yêu cầu<intrin.h>
, trong khi gcc yêu cầu <x86intrin.h>
. An #ifdef
chăm sóc gcc so với icc, nhưng clang dường như không cung cấp chúng ở bất kỳ đâu, ngoại trừ trong chế độ tương thích với MSVC-fms-extensions -fms-compatibility -fms-compatibility-version=17.00
. Và asm mà nó phát ra đối với họ thật tệ (thêm mặt nạ và CMOV).
- MSVC:
_rotr8
và_rotr16
.
- gcc và icc (không phải tiếng kêu):
<x86intrin.h>
cũng cung cấp __rolb
/ __rorb
cho 8-bit xoay trái / phải, __rolw
/ __rorw
(16-bit), __rold
/ __rord
(32-bit), __rolq
/ __rorq
(64-bit, chỉ được xác định cho các mục tiêu 64-bit). Đối với các vòng quay hẹp, việc triển khai sử dụng __builtin_ia32_rolhi
hoặc ...qi
, nhưng các vòng quay 32 và 64 bit được xác định bằng cách sử dụng shift / hoặc (không có bảo vệ chống lại UB, vì mã trong ia32intrin.h
chỉ phải hoạt động trên gcc cho x86). GNU C dường như không có bất kỳ __builtin_rotate
chức năng đa nền tảng nào theo cách của nó __builtin_popcount
(mở rộng đến bất kỳ chức năng tối ưu nào trên nền tảng đích, ngay cả khi nó không phải là một lệnh đơn lẻ). Hầu hết thời gian bạn nhận được mã tốt từ nhận dạng thành ngữ.
// 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
Có lẽ một số trình biên dịch không phải x86 cũng có bản chất, nhưng chúng ta đừng mở rộng câu trả lời wiki cộng đồng này để bao gồm tất cả chúng. (Có thể làm điều đó trong câu trả lời hiện có về bản chất ).
(Phiên bản cũ của câu trả lời này đã đề xuất asm nội tuyến cụ thể cho MSVC (chỉ hoạt động cho mã 32bit x86) hoặc http://www.devx.com/tips/Tip/14043 cho phiên bản C. Các nhận xét đang trả lời điều đó .)
Inline asm đánh bại nhiều tối ưu hóa , đặc biệt là kiểu MSVC vì nó buộc các đầu vào phải được lưu trữ / tải lại . Xoay nội tuyến GNU C được viết cẩn thận sẽ cho phép số đếm trở thành toán hạng ngay lập tức cho các số lượng dịch chuyển hằng thời gian biên dịch, nhưng nó vẫn không thể tối ưu hóa hoàn toàn nếu giá trị được dịch chuyển cũng là hằng số thời gian biên dịch sau khi nội tuyến. https://gcc.gnu.org/wiki/DontUseInlineAsm .