Đăng ký từ khóa trong C ++


89

Sự khác biệt giữa

int x=7;

register int x=7;

?

Tôi đang sử dụng C ++.


8
@GMan: ANSI C không cho phép lấy địa chỉ của một đối tượng đăng ký; hạn chế này không áp dụng cho C ++
Brian R. Bondy

1
@Brian: Hm, bạn nói đúng. Nó chỉ là một ghi chú bây giờ (rằng nó có thể sẽ bị bỏ qua nếu địa chỉ được lấy), nhưng không bắt buộc. Tốt để biết. (Chà, đại loại.: P)
GManNickG

8
Biểu quyết để mở lại registercó ngữ nghĩa khác nhau giữa C và C ++.
CB Bailey

3
do đó, trong C có thể cấm chuyển đổi mảng thành con trỏ bằng cách tạo một thanh ghi mảng: register int a[1];với khai báo đó, bạn không thể lập chỉ mục mảng đó. Nếu bạn cố gắng, bạn sẽ làm được UB
Johannes Schaub - litb

2
Thật vậy, tôi đã bỏ phiếu để mở lại. Tôi đã bỏ phiếu để đóng trước khi biết có sự khác biệt.
GManNickG

Câu trả lời:


24

Trong C ++ như nó đã tồn tại vào năm 2010, bất kỳ chương trình nào hợp lệ sử dụng các từ khóa "auto" hoặc "register" sẽ giống về mặt ngữ nghĩa với một chương trình đã bị xóa các từ khóa đó (trừ khi chúng xuất hiện trong các macro được xâu chuỗi hoặc các ngữ cảnh tương tự khác). Theo nghĩa đó, các từ khóa vô dụng đối với các chương trình biên dịch đúng cách. Mặt khác, các từ khóa có thể hữu ích trong một số ngữ cảnh macro nhất định để đảm bảo rằng việc sử dụng macro không đúng cách sẽ gây ra lỗi thời gian biên dịch hơn là tạo ra mã không có thật.

Trong C ++ 11 và các phiên bản mới hơn của ngôn ngữ, autotừ khóa đã được tái sử dụng để hoạt động như một kiểu giả cho các đối tượng được khởi tạo, trình biên dịch sẽ tự động thay thế bằng kiểu của biểu thức khởi tạo. Do đó, trong C ++ 03, khai báo: auto int i=(unsigned char)5;tương đương với int i=5;khi được sử dụng trong ngữ cảnh khối và auto i=(unsigned char)5;là một vi phạm ràng buộc. Trong C ++ 11, auto int i=(unsigned char)5;đã trở thành một vi phạm ràng buộc trong khi auto i=(unsigned char)5;trở thành tương đương với auto unsigned char i=5;.


22
Một ví dụ về bit cuối cùng có thể hữu ích.
Dennis Zickefoose,

14
Câu trả lời này không còn đúng nữa, kể từ năm 2011, autokhông thể đơn giản bỏ qua từ khóa ... Có lẽ bạn có thể cập nhật câu trả lời của mình.
Walter

2
@Walter: Bạn có thể trích dẫn những gì đã thay đổi không? Tôi đã không theo dõi tất cả các thay đổi ngôn ngữ.
supercat

2
@supercat, có, hiện tại, nhưng registerkhông được dùng nữa và sẽ có một đề xuất xóa nó cho C ++ 17.
Jonathan Wakely

3
Theo en.cppreference.com/w/cpp/language/auto , bài đăng C ++ 11, autohiện được sử dụng để khấu trừ kiểu tự động Nhưng trước đó, nó được sử dụng để chỉ định rằng bạn muốn biến của mình được lưu trữ "tự động" ( do đó, tôi đoán trên ngăn xếp ) trái ngược với từ khóa register(có nghĩa là "đăng ký của bộ xử lý"):
Guillaume

97

register là một gợi ý cho trình biên dịch, khuyên nó lưu trữ biến đó trong thanh ghi bộ xử lý thay vì bộ nhớ (ví dụ: thay vì ngăn xếp).

Trình biên dịch có thể tuân theo gợi ý đó hoặc không.

Theo Herb Sutter trong "Từ khóa không phải (hoặc, nhận xét bằng tên khác)" :

Bộ chỉ định thanh ghi có cùng ngữ nghĩa với bộ chỉ định tự động ...


2
Tuy nhiên, kể từ C ++ 17, nó không được dùng nữa, không được sử dụng và dành riêng.
ZachB

@ZachB, điều này không chính xác; Thanh ghi được dành riêng trong C ++ 17 nhưng nó vẫn hoạt động và hoạt động gần như giống hệt thanh ghi C.
Lewis Kelsey

@LewisKelsey Nó không được sử dụng và được bảo lưu trong đặc tả C ++ 17; nó không phải là một storage-class-specifiertrong những ngữ pháp và không có ngữ nghĩa xác định. Một trình biên dịch phù hợp có thể gây ra lỗi giống như Clang. Tuy nhiên, một số triển khai vẫn cho phép nó và bỏ qua nó (MSVC, ICC) hoặc sử dụng nó như một gợi ý tối ưu hóa (GCC). Xem open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0001r1.html . Tuy nhiên, tôi đã sai ở một điểm: nó không được chấp nhận trong C ++ 11.
ZachB


26

Với các trình biên dịch ngày nay, có lẽ không có gì. Ban đầu là một gợi ý để đặt một biến trong sổ đăng ký để truy cập nhanh hơn, nhưng hầu hết các trình biên dịch ngày nay đều bỏ qua gợi ý đó và tự quyết định.


9

Gần như chắc chắn không có gì.

registerlà một gợi ý cho trình biên dịch mà bạn dự định sử dụng xnhiều và bạn nghĩ rằng nó nên được đặt trong một sổ đăng ký.

Tuy nhiên, các trình biên dịch hiện nay giỏi hơn nhiều trong việc xác định giá trị nào nên được đặt trong thanh ghi so với lập trình viên trung bình (hoặc thậm chí là chuyên gia), vì vậy các trình biên dịch chỉ cần bỏ qua từ khóa và làm những gì họ muốn.



7

Các registertừ khóa là hữu ích cho:

  • Lắp ráp nội tuyến.
  • Lập trình C / C ++ thành thạo.
  • Khai báo các biến có thể lưu trong bộ nhớ cache.

Ví dụ về hệ thống hiệu quả, trong đó registertừ khóa được yêu cầu:

typedef unsigned long long Out;
volatile Out out,tmp;
Out register rax asm("rax");
asm volatile("rdtsc":"=A"(rax));
out=out*tmp+rax;

Nó đã không được chấp nhận kể từ C ++ 11 và không được sử dụng và được bảo lưu trong C ++ 17 .


2
Và tôi muốn nói thêm rằng từ khóa 'đăng ký' sẽ chỉ hữu ích trên vi điều khiển chạy một chương trình C ++ duy nhất không có luồng và không có đa tác vụ. Chương trình C ++ sẽ phải sở hữu toàn bộ CPU để đảm bảo rằng biến 'thanh ghi' sẽ không bị di chuyển khỏi các thanh ghi CPU đặc biệt.
Santiago Villafuerte

@SantiagoVillafuerte bạn có muốn thêm nó để chỉnh sửa câu trả lời không?
ncomputers

Tôi không chắc về câu trả lời của mình ... mặc dù nghe có vẻ hợp lý. Tôi muốn để lại nó dưới dạng một bình luận để những người khác chấp thuận nó hoặc không chấp nhận nó.
Santiago Villafuerte

1
@SantiagoVillafuerte Điều này không thực sự đúng, trong các hệ thống đa nhiệm khi chuyển đổi ngữ cảnh thì hệ điều hành - không phải ứng dụng - chịu trách nhiệm lưu / khôi phục các thanh ghi. Vì bạn không phải chuyển đổi ngữ cảnh sau mỗi lệnh CPU, nên việc đưa mọi thứ vào thanh ghi là hoàn toàn có ý nghĩa. Các câu trả lời khác ở đây (trình biên dịch chỉ đơn giản là không quan tâm đến ý kiến ​​của bạn khi nói đến phân bổ đăng ký) chính xác hơn.
Khối

Ví dụ bạn đã hiển thị thực sự đang sử dụng phần mở rộng Biến đăng ký rõ ràng của GCC, phần mở rộng này khác với phần bổ sung registerlớp lưu trữ và vẫn được GCC hỗ trợ.
ZachB

1

Hãy xem xét một trường hợp khi trình tối ưu hóa của trình biên dịch có hai biến và buộc phải đổ một biến vào ngăn xếp. Nó đã xảy ra rằng cả hai biến có cùng trọng số đối với trình biên dịch. Do không có sự khác biệt, trình biên dịch sẽ tùy ý đổ một trong các biến. Mặt khác, registertừ khóa cung cấp cho trình biên dịch một gợi ý biến nào sẽ được truy cập thường xuyên hơn. Nó tương tự như lệnh tìm nạp trước x86, nhưng dành cho trình tối ưu hóa trình biên dịch.

Rõ ràng registerlà các gợi ý tương tự như các gợi ý xác suất nhánh do người dùng cung cấp và có thể được suy ra từ các gợi ý xác suất này. Nếu trình biên dịch biết rằng một số nhánh được sử dụng thường xuyên, nó sẽ giữ các biến liên quan đến nhánh trong thanh ghi. Vì vậy, tôi khuyên bạn nên quan tâm nhiều hơn đến các gợi ý về nhánh, và hãy quên đi register. Lý tưởng nhất là trình biên dịch của bạn nên giao tiếp bằng cách nào đó với trình biên dịch và giúp bạn không phải nghĩ về những sắc thái như vậy.


1

Kể từ gcc 9.3, việc biên dịch bằng cách sử dụng -std=c++2a, register tạo ra một cảnh báo trình biên dịch, nhưng nó vẫn có tác dụng mong muốn và hoạt động giống như C registerkhi biên dịch mà không có cờ -O1 –- Ofast tối ưu hóa về câu trả lời này . Tuy nhiên, sử dụng clang ++ - 7 gây ra lỗi trình biên dịch. Vì vậy, đúng vậy, các registertối ưu hóa chỉ tạo ra sự khác biệt khi biên dịch tiêu chuẩn không có cờ -O tối ưu hóa, nhưng chúng là những tối ưu hóa cơ bản mà trình biên dịch sẽ tìm ra ngay cả với -O1.

Sự khác biệt duy nhất là trong C ++, bạn được phép lấy địa chỉ của biến đăng ký, điều đó có nghĩa là việc tối ưu hóa chỉ xảy ra nếu bạn không lấy địa chỉ của biến hoặc bí danh của nó (để tạo con trỏ) hoặc tham chiếu của nó trong mã (chỉ trên - O0, vì một tham chiếu cũng có địa chỉ, vì đó là một con trỏ const trên ngăn xếp , giống như một con trỏ có thể được tối ưu hóa khỏi ngăn xếp nếu biên dịch bằng -Ofast, ngoại trừ chúng sẽ không bao giờ xuất hiện trên ngăn xếp bằng cách sử dụng -Ofast, vì không giống như một con trỏ, chúng không thể được tạo volatilevà không thể lấy địa chỉ của chúng), nếu không nó sẽ hoạt động như bạn chưa sử dụng registervà giá trị sẽ được lưu trữ trên ngăn xếp.

Trên -O0, một sự khác biệt khác là const registertrên gcc C và gcc C ++ không hoạt động giống nhau. Trên gcc C, const registerhoạt động như thế nào register, vì phạm vi khối constkhông được tối ưu hóa trên gcc. Trên clang C, registerkhông có gì và chỉ constáp dụng tối ưu phạm vi khối. Trên gcc C, registertối ưu hóa được áp dụng nhưng constở phạm vi khối không có tối ưu hóa. Trên gcc C ++, cả tối ưu hóa phạm vi registerconstkhối kết hợp với nhau.

#include <stdio.h> //yes it's C code on C++
int main(void) {
  const register int i = 3;
  printf("%d", i);
  return 0;
}

int i = 3;:

.LC0:
  .string "%d"
main:
  push rbp
  mov rbp, rsp
  sub rsp, 16
  mov DWORD PTR [rbp-4], 3
  mov eax, DWORD PTR [rbp-4]
  mov esi, eax
  mov edi, OFFSET FLAT:.LC0
  mov eax, 0
  call printf
  mov eax, 0
  leave
  ret

register int i = 3;:

.LC0:
  .string "%d"
main:
  push rbp
  mov rbp, rsp
  push rbx
  sub rsp, 8
  mov ebx, 3
  mov esi, ebx
  mov edi, OFFSET FLAT:.LC0
  mov eax, 0
  call printf
  mov eax, 0
  mov rbx, QWORD PTR [rbp-8] //callee restoration
  leave
  ret

const int i = 3;

.LC0:
  .string "%d"
main:
  push rbp
  mov rbp, rsp
  sub rsp, 16
  mov DWORD PTR [rbp-4], 3 //still saves to stack
  mov esi, 3 //immediate substitution
  mov edi, OFFSET FLAT:.LC0
  mov eax, 0
  call printf
  mov eax, 0
  leave
  ret

const register int i = 3;

.LC0:
  .string "%d"
main:
  push rbp
  mov rbp, rsp
  mov esi, 3 //loads straight into esi saving rbx push/pop and extra indirection (because C++ block-scope const is always substituted immediately into the instruction)
  mov edi, OFFSET FLAT:.LC0 // can't optimise away because printf only takes const char*
  mov eax, 0 //zeroed: https://stackoverflow.com/a/6212755/7194773
  call printf
  mov eax, 0 //default return value of main is 0
  pop rbp //nothing else pushed to stack -- more efficient than leave (rsp == rbp already)
  ret

registeryêu cầu trình biên dịch biết 1) lưu trữ một biến cục bộ trong một thanh ghi đã lưu callee, trong trường hợp này rbx, và 2) tối ưu hóa việc ghi ngăn xếp nếu địa chỉ của biến không bao giờ được lấy . constyêu cầu trình biên dịch thay thế giá trị ngay lập tức (thay vì gán cho nó một thanh ghi hoặc tải nó từ bộ nhớ) và ghi biến cục bộ vào ngăn xếp như hành vi mặc định. const registerlà sự kết hợp của những tối ưu được khuyến khích này. Đây là đường nét mảnh mai như nó có được.

Ngoài ra, trên gcc C và C ++, registertự nó dường như tạo ra khoảng trống 16 byte ngẫu nhiên trên ngăn xếp cho cục bộ đầu tiên trên ngăn xếp, điều này không xảy ra với const register.

Biên dịch bằng -Ofast tuy nhiên; registercó 0 tác dụng tối ưu hóa bởi vì nếu nó có thể được đưa vào sổ đăng ký hoặc được thực hiện ngay lập tức, nó sẽ luôn như vậy và nếu không thể thì sẽ không; constvẫn tối ưu hóa tải trên C và C ++ nhưng chỉ ở phạm vi tệp ; volatilevẫn buộc các giá trị được lưu trữ và tải từ ngăn xếp.

.LC0:
  .string "%d"
main:
  //optimises out push and change of rbp
  sub rsp, 8 //https://stackoverflow.com/a/40344912/7194773
  mov esi, 3
  mov edi, OFFSET FLAT:.LC0
  xor eax, eax //xor 2 bytes vs 5 for mov eax, 0
  call printf
  xor eax, eax
  add rsp, 8
  ret
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.