Câu hỏi Các toán tử dịch chuyển bit (bit-shift) là gì và chúng hoạt động như thế nào?


Tôi đã cố gắng để tìm hiểu C trong thời gian rảnh rỗi của tôi, và các ngôn ngữ khác (C #, Java, vv) có cùng một khái niệm (và thường các nhà khai thác tương tự) ...

Điều tôi đang tự hỏi là, ở cấp độ cốt lõi, những gì dịch chuyển bit (<<, >>, >>>), những vấn đề gì có thể giúp giải quyết, và những gì gotchas ẩn nấp quanh khúc cua? Nói cách khác, một hướng dẫn cho người mới bắt đầu tuyệt đối để thay đổi chút ít trong tất cả sự tốt lành của nó.


1189
2017-09-26 19:47


gốc


Các trường hợp chức năng hoặc phi chức năng mà bạn sẽ sử dụng bithifting trong 3GL là rất ít. - Troy DeMonbreun
Sau khi đọc những câu trả lời này, bạn có thể muốn xem các liên kết sau: graphics.stanford.edu/~seander/bithacks.html & jjj.de/bitwizardry/bitwizardrypage.html - claws
Điều quan trọng cần lưu ý là chuyển bit là cực kỳ dễ dàng và nhanh chóng cho các máy tính để làm. Bằng cách tìm cách sử dụng bit-shifting trong chương trình của bạn, bạn có thể giảm đáng kể thời gian sử dụng bộ nhớ và thời gian thực thi. - Hoytman


Các câu trả lời:


Các toán tử dịch chuyển bit thực hiện chính xác tên của chúng. Chúng chuyển bit. Dưới đây là phần giới thiệu ngắn gọn (hoặc không ngắn gọn) cho các toán tử dịch chuyển khác nhau.

Các nhà khai thác

  • >> là toán tử dịch số học (hoặc đã ký).
  • >>> là toán tử dịch chuyển hợp lý (hoặc không dấu).
  • << là toán tử dịch chuyển trái, và đáp ứng nhu cầu của cả hai thay đổi logic và số học.

Tất cả các toán tử này có thể được áp dụng cho các giá trị số nguyên (int, longcó thể short và byte hoặc là char). Trong một số ngôn ngữ, áp dụng toán tử shift cho bất kỳ kiểu dữ liệu nào nhỏ hơn int tự động thay đổi kích thước toán hạng thành một int.

Lưu ý rằng <<< không phải là một toán tử, bởi vì nó sẽ dư thừa. Cũng lưu ý rằng C và C ++ không phân biệt giữa các toán tử dịch chuyển phải. Họ chỉ cung cấp >> toán tử và hành vi dịch chuyển phải là việc triển khai thực hiện được xác định cho các loại đã ký.


Phím trái (<<)

Số nguyên được lưu trữ, trong bộ nhớ, như một chuỗi các bit. Ví dụ, số 6 được lưu trữ dưới dạng 32 bit int sẽ là:

00000000 00000000 00000000 00000110

Chuyển mẫu bit này sang bên trái một vị trí (6 << 1) sẽ dẫn đến số 12:

00000000 00000000 00000000 00001100

Như bạn có thể thấy, các chữ số đã dịch chuyển sang trái bởi một vị trí, và chữ số cuối cùng bên phải được lấp đầy bằng số không. Bạn cũng có thể lưu ý rằng việc dịch chuyển sang trái tương đương với phép nhân bằng lũy ​​thừa của 2. Vì vậy, 6 << 1 tương đương với 6 * 26 << 3 tương đương với 6 * 8. Trình biên dịch tối ưu hóa tốt sẽ thay thế phép nhân với ca làm việc khi có thể.

Chuyển dịch không tròn

Xin lưu ý rằng đây là những không phải thay đổi tròn. Chuyển giá trị này sang bên trái bởi một vị trí (3,758,096,384 << 1):

11100000 00000000 00000000 00000000

kết quả trong 3.221.225.472:

11000000 00000000 00000000 00000000

Chữ số bị dịch chuyển "tắt đầu" bị mất. Nó không quấn quanh.


Phép dịch hợp lý (>>>)

Một sự thay đổi hợp lý là trò chuyện với sự dịch chuyển trái. Thay vì di chuyển các bit sang bên trái, chúng đơn giản di chuyển sang phải. Ví dụ: chuyển số 12:

00000000 00000000 00000000 00001100

bên phải bởi một vị trí (12 >>> 1) sẽ lấy lại bản gốc 6 của chúng tôi:

00000000 00000000 00000000 00000110

Vì vậy, chúng ta thấy rằng dịch chuyển sang phải tương đương với phân chia theo lũy thừa của 2.

Các bit bị mất đã biến mất

Tuy nhiên, một sự thay đổi không thể đòi lại các bit "bị mất". Ví dụ: nếu chúng ta thay đổi mẫu này:

00111000 00000000 00000000 00000110

sang 4 vị trí bên trái (939,524,102 << 4), chúng tôi nhận được 2.147.483.744:

10000000 00000000 00000000 01100000

và sau đó chuyển trở lại ((939,524,102 << 4) >>> 4) chúng tôi nhận được 134.217.734:

00001000 00000000 00000000 00000110

Chúng tôi không thể lấy lại giá trị ban đầu của chúng tôi khi chúng tôi đã mất bit.


Chuyển số học đúng (>>)

Sự dịch chuyển số học chính xác giống như sự dịch chuyển hợp lý, ngoại trừ thay vì padding bằng không, nó có phần quan trọng nhất. Điều này là do bit quan trọng nhất là ký tên bit, hoặc bit phân biệt các số âm và dương. Bằng cách đệm với bit quan trọng nhất, phép dịch số học là dấu hiệu bảo quản.

Ví dụ: nếu chúng tôi diễn giải mẫu bit này dưới dạng số âm:

10000000 00000000 00000000 01100000

chúng ta có số -2,147,483,552. Chuyển nó sang 4 vị trí bên phải với phép dịch số học (-2,147,483,552 >> 4) sẽ cho chúng ta:

11111000 00000000 00000000 00000110

hoặc số -134,217,722.

Vì vậy, chúng tôi thấy rằng chúng tôi đã giữ nguyên dấu hiệu của các số âm của chúng tôi bằng cách sử dụng phép dịch số học, thay vì sự dịch chuyển đúng logic. Và một lần nữa, chúng ta thấy rằng chúng ta đang thực hiện phân chia theo sức mạnh của 2.


1508
2017-09-26 20:46



Câu trả lời sẽ làm cho nó rõ ràng hơn rằng đây là một câu trả lời cụ thể cho Java. Không có toán tử >>> trong C / C ++ hoặc C #, và có hay không >> tuyên truyền ký hiệu là việc triển khai thực hiện được xác định trong C / C ++ (một bản ghi có tiềm năng lớn) - Michael Burr
Câu trả lời là hoàn toàn không chính xác trong ngữ cảnh của ngôn ngữ C. Không có sự phân chia có ý nghĩa thành các thay đổi "số học" và "hợp lý" trong C. Trong C, các ca làm việc như mong đợi trên các giá trị chưa ký và trên các giá trị đã ký tích cực - chúng chỉ chuyển bit. Trên các giá trị âm, dịch chuyển phải được thực hiện (nghĩa là không có gì có thể nói về những gì nó nói chung), và dịch chuyển trái đơn giản bị cấm - nó tạo ra hành vi không xác định. - AnT
Audrey, chắc chắn có một sự khác biệt giữa số học và chuyển dịch đúng logic. C chỉ đơn giản là lá thực hiện lựa chọn được xác định. Và sự thay đổi trái trên các giá trị tiêu cực chắc chắn không bị cấm. Chuyển 0xff000000 sang bên trái một chút và bạn sẽ nhận được 0xfe000000. - Derek Park
A good optimizing compiler will substitute shifts for multiplications when possible.Gì? Bitshifts là đơn đặt hàng của cường độ nhanh hơn khi nó đi xuống đến các hoạt động cấp thấp của một CPU, một trình biên dịch tối ưu hóa tốt sẽ làm chính xác ngược lại, nghĩa là, biến phép nhân bình thường bằng sức mạnh của hai ca thành bit. - Mahn
@Mahn, bạn đang đọc nó ngược từ ý định của tôi. Thay thế Y cho X có nghĩa là thay thế X bằng Y. Y là thay thế cho X. Vì vậy, thay đổi là thay thế cho phép nhân. - Derek Park


Giả sử chúng ta có một byte đơn:

0110110

Áp dụng một bithift đơn bên trái được chúng tôi:

1101100

Số không ngoài cùng bên trái đã được chuyển ra khỏi byte, và một số không mới được nối vào đầu bên phải của byte.

Các bit không tái đầu tư; chúng bị loại bỏ. Điều đó có nghĩa là nếu bạn rời khỏi ca khúc 1101100 và sau đó chuyển sang phải, bạn sẽ không nhận được kết quả tương tự.

Chuyển sang trái bằng N tương đương với nhân với 2N.

Di chuyển sang phải bằng N là (nếu bạn đang sử dụng bổ sung của những người) tương đương với chia cho 2N và làm tròn số không.

Bitshifting có thể được sử dụng cho phép nhân nhanh và nhân rộng, miễn là bạn đang làm việc với sức mạnh của 2. Hầu như tất cả các thói quen đồ họa mức thấp đều sử dụng bithifting.

Ví dụ, cách trở lại trong những ngày xa xưa, chúng tôi sử dụng chế độ 13h (320x200 256 màu) cho các trò chơi. Trong Chế độ 13h, bộ nhớ video được đặt tuần tự trên mỗi pixel. Điều đó có nghĩa là để tính toán vị trí cho pixel, bạn sẽ sử dụng toán học sau:

memoryOffset = (row * 320) + column

Bây giờ, trở lại trong ngày và tuổi tác, tốc độ là rất quan trọng, vì vậy chúng tôi sẽ sử dụng bithifts để thực hiện thao tác này.

Tuy nhiên, 320 không phải là một sức mạnh của hai, do đó, để có được xung quanh này, chúng ta phải tìm ra sức mạnh của hai mà cộng lại làm cho 320 là gì:

(row * 320) = (row * 256) + (row * 64)

Bây giờ chúng ta có thể chuyển đổi nó thành những thay đổi bên trái:

(row * 320) = (row << 8) + (row << 6)

Để có kết quả cuối cùng của:

memoryOffset = ((row << 8) + (row << 6)) + column

Bây giờ chúng ta có cùng độ lệch như trước, ngoại trừ thay vì phép toán nhân bội đắt tiền, chúng ta sử dụng hai bithifts ... trong x86 nó sẽ giống như thế này (lưu ý, nó là mãi mãi kể từ khi tôi lắp ráp xong (ghi chú của biên tập viên: đã sửa chữa) một vài lỗi và thêm một ví dụ 32-bit)):

mov ax, 320; 2 cycles
mul word [row]; 22 CPU Cycles
mov di,ax; 2 cycles
add di, [column]; 2 cycles
; di = [row]*320 + [column]

; 16-bit addressing mode limitations:
; [di] is a valid addressing mode, but [ax] isn't, otherwise we could skip the last mov

Tổng cộng: 28 chu kỳ trên bất kỳ CPU cổ nào có thời gian này.

Vrs

mov ax, [row]; 2 cycles
mov di, ax; 2
shl ax, 6;  2
shl di, 8;  2
add di, ax; 2    (320 = 256+64)
add di, [column]; 2
; di = [row]*(256+64) + [column]

12 chu kỳ trên cùng một CPU cổ đại.

Có, chúng tôi sẽ làm việc này khó khăn để cạo 16 chu kỳ CPU.

Ở chế độ 32 hoặc 64 bit, cả hai phiên bản đều nhanh hơn và nhanh hơn rất nhiều. CPU thực thi không theo trật tự hiện đại như Intel Skylake (xem http://agner.org/optimize/) có phần cứng rất nhanh nhân (độ trễ thấp và thông lượng cao), vì vậy mức tăng nhỏ hơn nhiều. AMD Bulldozer-gia đình là một chút chậm hơn, đặc biệt là cho nhân 64-bit. Trên các CPU Intel và AMD Ryzen, hai ca có độ trễ thấp hơn một chút nhưng nhiều hướng dẫn hơn so với số nhân (có thể dẫn đến thông lượng thấp hơn):

imul edi, [row], 320    ; 3 cycle latency from [row] being ready
add  edi, [column]      ; 1 cycle latency (from [column] and edi being ready).
; edi = [row]*(256+64) + [column],  in 4 cycles from [row] being ready.

so với

mov edi, [row]
shl edi, 6               ; row*64.   1 cycle latency
lea edi, [edi + edi*4]   ; row*(64 + 64*4).  1 cycle latency
add edi, [column]        ; 1 cycle latency from edi and [column] both being ready
; edi = [row]*(256+64) + [column],  in 3 cycles from [row] being ready.

Trình biên dịch sẽ làm điều này cho bạn: Xem cách gcc, clang và MSVC đều sử dụng shift + lea khi tối ưu hóa return 320*row + col;.

Điều thú vị nhất cần lưu ý ở đây là x86 có hướng dẫn thay đổi và thêm (LEA) có thể làm các thay đổi nhỏ trái và thêm cùng một lúc, với hiệu suất như và add chỉ dẫn. ARM thậm chí còn mạnh mẽ hơn: một toán hạng của bất kỳ lệnh nào có thể được chuyển sang trái hoặc sang phải miễn phí. Vì vậy, mở rộng bởi một hằng số biên dịch-thời gian được biết đến là một sức mạnh-of-2 có thể thậm chí còn hiệu quả hơn so với một nhân.


OK, trở lại trong những ngày hiện đại ... một cái gì đó hữu ích hơn bây giờ sẽ được sử dụng bithifting để lưu trữ hai giá trị 8-bit trong một số nguyên 16-bit. Ví dụ, trong C #:

// Byte1: 11110000
// Byte2: 00001111

Int16 value = ((byte)(Byte1 >> 8) | Byte2));

// value = 000011111110000;

Trong C ++, trình biên dịch nên làm điều này cho bạn nếu bạn sử dụng struct với hai thành viên 8 bit, nhưng trong thực tế không phải lúc nào.


169
2017-09-26 19:55



Mở rộng này, trên bộ xử lý Intel (và rất nhiều người khác) nó nhanh hơn để làm điều này: int c, d; c = d << 2; Hơn thế này: c = 4 * d; Đôi khi, ngay cả "c = d << 2 + d << 1" nhanh hơn "c = 6 * d" !! Tôi đã sử dụng các thủ thuật này một cách rộng rãi cho các chức năng đồ họa trong thời đại DOS, tôi không nghĩ chúng hữu ích nữa ... - Joe Pineda
@ Joe: Nếu những thủ thuật này không hữu ích nữa, điều đó có nghĩa là các nhà biên dịch đang sử dụng chúng ngay bây giờ? - James Thompson
@ James: không hoàn toàn, ngày nay nó là firmware của card màn hình bao gồm mã như thế, được thực thi bởi GPU chứ không phải CPU. Vì vậy, về lý thuyết bạn không cần phải thực hiện mã như thế này (hoặc giống như chức năng gốc nghịch đảo ma thuật đen của Carmack) cho các chức năng đồ họa :-) - Joe Pineda
@JoePineda @james Các nhà văn biên dịch chắc chắn đang sử dụng chúng. Nếu bạn viết c=4*d bạn sẽ nhận được một sự thay đổi. Nếu bạn viết k = (n<0) có thể được thực hiện với ca làm việc: k = (n>>31)&1 để tránh một chi nhánh. Tóm lại, sự cải thiện này trong tính minh bạch của trình biên dịch có nghĩa là bây giờ không cần thiết phải sử dụng các thủ thuật này trong mã C, và chúng thỏa hiệp khả năng đọc và tính di động. Vẫn còn rất tốt để biết họ nếu bạn đang viết ví dụ: Mã vectơ SSE; hoặc bất kỳ tình huống nào bạn cần nhanh và có một mẹo mà trình biên dịch không sử dụng (ví dụ: mã GPU). - greggo
Một ví dụ điển hình khác: điều rất phổ biến là if(x >= 1 && x <= 9) có thể được thực hiện như if( (unsigned)(x-1) <=(unsigned)(9-1))  Thay đổi hai thử nghiệm có điều kiện thành một có thể là một lợi thế tốc độ lớn; đặc biệt là khi nó cho phép thực thi được xác thực thay vì các nhánh. Tôi đã sử dụng điều này trong nhiều năm (nơi được biện minh) cho đến khi tôi nhận thấy abt 10 năm trước rằng các trình biên dịch đã bắt đầu thực hiện phép biến đổi này trong trình tối ưu hóa, sau đó tôi dừng lại. Vẫn còn tốt để biết, vì có những tình huống tương tự, nơi trình biên dịch không thể thực hiện chuyển đổi cho bạn. Hoặc nếu bạn đang làm việc trên một trình biên dịch. - greggo


Các hoạt động bitwise, bao gồm cả thay đổi bit, là nền tảng cho phần cứng cấp thấp hoặc lập trình được nhúng. Nếu bạn đọc một đặc điểm kỹ thuật cho một thiết bị hoặc thậm chí một số định dạng tệp nhị phân, bạn sẽ thấy các byte, từ và dwords, được chia thành các bitfield được liên kết không byte, chứa các giá trị quan tâm khác nhau. Việc truy cập các trường bit để đọc / ghi này là cách sử dụng phổ biến nhất.

Một ví dụ thực sự đơn giản trong lập trình đồ họa là một pixel 16 bit được biểu diễn như sau:

  bit | 15| 14| 13| 12| 11| 10| 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1  | 0 |
      |       Blue        |         Green         |       Red          |

Để đạt được giá trị màu xanh lá cây, bạn sẽ làm điều này:

 #define GREEN_MASK  0x7E0
 #define GREEN_OFFSET  5

 // Read green
 uint16_t green = (pixel & GREEN_MASK) >> GREEN_OFFSET;

Giải trình

Để có được giá trị của màu xanh lá cây CHỈ, bắt đầu tại offset 5 và kết thúc ở 10 (tức là 6-bit dài), bạn cần sử dụng mặt nạ (bit), khi được áp dụng cho toàn bộ pixel 16 bit, sẽ mang lại chỉ các bit chúng ta quan tâm.

#define GREEN_MASK  0x7E0

Mặt nạ thích hợp là 0x7E0, trong đó nhị phân là 0000011111100000 (năm 2016 là thập phân).

uint16_t green = (pixel & GREEN_MASK) ...;

Để áp dụng mặt nạ, bạn sử dụng toán tử AND (&).

uint16_t green = (pixel & GREEN_MASK) >> GREEN_OFFSET;

Sau khi áp dụng mặt nạ, bạn sẽ kết thúc với một số 16-bit mà thực sự chỉ là một số 11-bit kể từ khi MSB của nó là trong bit thứ 11. Màu xanh lá cây thực sự chỉ dài 6 bit, vì vậy chúng ta cần phải giảm tỷ lệ xuống bằng cách sử dụng dịch chuyển đúng (11 - 6 = 5), do đó việc sử dụng 5 là bù trừ (#define GREEN_OFFSET 5).

Cũng phổ biến là sử dụng các thay đổi bit để nhân nhanh và chia theo lũy thừa của 2:

 i <<= x;  // i *= 2^x;
 i >>= y;  // i /= 2^y;

83
2017-09-26 22:22



0x7e0 tương tự như 11111100000 là năm thập phân. - Saheed


Bit Masking & Shifting

Chuyển dịch bit thường được sử dụng trong lập trình đồ họa mức thấp. Ví dụ: giá trị màu pixel cho trước được mã hóa bằng từ 32 bit.

 Pixel-Color Value in Hex:    B9B9B900
 Pixel-Color Value in Binary: 10111001  10111001  10111001  00000000

Để hiểu rõ hơn, cùng một giá trị nhị phân được gắn nhãn với phần nào thể hiện phần màu nào.

                                 Red     Green     Blue       Alpha
 Pixel-Color Value in Binary: 10111001  10111001  10111001  00000000

Ví dụ, chúng ta muốn lấy giá trị màu xanh của màu pixel này. Chúng ta có thể dễ dàng có được giá trị đó bởi che mặt nạ và chuyển dịch.

Mặt nạ của chúng tôi:

                  Red      Green      Blue      Alpha
 color :        10111001  10111001  10111001  00000000
 green_mask  :  00000000  11111111  00000000  00000000

 masked_color = color & green_mask

 masked_color:  00000000  10111001  00000000  00000000

Hợp lý & toán tử đảm bảo rằng chỉ các giá trị trong đó mặt nạ là 1 được giữ. Điều cuối cùng chúng ta phải làm là lấy giá trị số nguyên chính xác bằng cách dịch chuyển tất cả các bit đó sang bên phải 16 vị trí (sự thay đổi hợp lý).

 green_value = masked_color >>> 16

Ví dụ, chúng tôi có số nguyên đại diện cho số lượng màu xanh lá cây trong màu pixel:

 Pixels-Green Value in Hex:     000000B9
 Pixels-Green Value in Binary:  00000000 00000000 00000000 10111001 
 Pixels-Green Value in Decimal: 185

Điều này thường được sử dụng để mã hóa hoặc giải mã các định dạng hình ảnh như jpg,png,....


39
2018-03-31 10:49





Một lưu ý là sau đây là phụ thuộc thực hiện (theo tiêu chuẩn ANSI):

char x = -1;
x >> 1;

x bây giờ có thể là 127 (01111111) hoặc vẫn còn -1 (11111111).

Trong thực tế, nó thường là sau này.


26
2017-09-26 20:07



Nếu tôi nhớ chính xác, tiêu chuẩn ANSI C nói rõ điều này phụ thuộc vào việc triển khai thực hiện, vì vậy bạn cần kiểm tra tài liệu của trình biên dịch để xem nó được thực hiện như thế nào nếu bạn muốn thay đổi số nguyên đã ký trên mã của bạn. - Joe Pineda
Có, tôi chỉ muốn nhấn mạnh tiêu chuẩn ANSI nói như vậy, nó không phải là một trường hợp mà các nhà cung cấp chỉ đơn giản là không tuân theo các tiêu chuẩn hoặc tiêu chuẩn nói gì về trường hợp cụ thể này. - Joe Pineda
@ Hashelly Số học của nó và sự thay đổi đúng hợp lý. - abc


Lưu ý rằng trong việc thực hiện Java, số lượng bit cần dịch được thay đổi theo kích thước của nguồn.

Ví dụ:

(long) 4 >> 65

bằng 2. Bạn có thể mong đợi việc chuyển các bit sang bên phải 65 lần sẽ không phải tất cả mọi thứ, nhưng nó thực sự tương đương với:

(long) 4 >> (65 % 64)

Điều này đúng với <<, >>, và >>>. Tôi đã không thử nó bằng các ngôn ngữ khác.


8
2017-08-28 13:16



Huh, thú vị! Trong C, đây là kỹ thuật hành vi không xác định. gcc 5.4.0 đưa ra một cảnh báo, nhưng cho 2 cho 5 >> 65; cũng. - pizzapants184


Tôi chỉ viết mẹo và thủ thuật, có thể hữu ích trong các bài kiểm tra / bài kiểm tra.

  1. n = n*2: n = n<<1
  2. n = n/2: n = n>>1
  3. Kiểm tra nếu n là lũy thừa của 2 (1,2,4,8, ...): kiểm tra !(n & (n-1))
  4. Bắt xth chút n: n |= (1 << x)
  5. Kiểm tra xem x là chẵn hay lẻ: x&1 == 0 (cũng)
  6. Chuyển đổi nth bit x: x ^ (1<<n)

6
2017-10-11 22:43



Phải có thêm một vài điều mà bạn biết bây giờ? - ryyker
@ryyker Tôi đã thêm một vài chi tiết. Tôi sẽ cố gắng tiếp tục cập nhật nó :) - Ravi Prakash


Hãy lưu ý rằng chỉ có phiên bản 32 bit của PHP có sẵn trên nền tảng Windows.

Sau đó, nếu bạn thay đổi ví dụ << hoặc >> nhiều hơn 31 bit, kết quả là không thể dự đoán được. Thông thường số gốc thay vì số không sẽ được trả lại và có thể là lỗi thực sự phức tạp.

Tất nhiên nếu bạn sử dụng phiên bản 64 bit của PHP (unix), bạn nên tránh chuyển hơn 63 bit. Tuy nhiên, ví dụ, MySQL sử dụng BIGINT 64 bit, do đó không nên có bất kỳ vấn đề tương thích nào.

CẬP NHẬT: Từ PHP7 Windows, các bản dựng php cuối cùng có thể sử dụng các số nguyên 64 bit đầy đủ: Kích thước của một số nguyên là nền tảng phụ thuộc, mặc dù giá trị tối đa khoảng hai tỷ là giá trị thông thường (đó là 32 bit đã ký). Nền tảng 64 bit thường có giá trị tối đa khoảng 9E18, ngoại trừ trên Windows trước khi PHP 7, nơi nó luôn luôn là 32 bit.


-2
2017-10-23 14:28