ลงทะเบียนคำสำคัญใน C ++


89

อะไรคือความแตกต่างระหว่าง

int x=7;

และ

register int x=7;

เหรอ?

ฉันใช้ C ++


8
@GMan: ANSI C ไม่อนุญาตให้รับที่อยู่ของวัตถุลงทะเบียน ข้อ จำกัด นี้ใช้ไม่ได้กับ C ++
Brian R.Bondy

1
@ ไบรอัน: หืมคุณพูดถูก ตอนนี้อยู่ในบันทึกย่อแล้ว (ซึ่งอาจถูกละเว้นหากใช้ที่อยู่) แต่ไม่ได้รับคำสั่ง ดีแล้วที่รู้. (เรียงลำดับจาก: P)
GManNickG

8
การลงคะแนนเพื่อเปิดใหม่registerมีความหมายที่แตกต่างกันระหว่าง C และ C ++
CB Bailey

3
ด้วยเหตุนี้ใน C จึงเป็นไปได้ที่จะห้ามการแปลงอาร์เรย์เป็นตัวชี้โดยการลงทะเบียนอาร์เรย์: register int a[1];ด้วยการประกาศดังกล่าวคุณไม่สามารถจัดทำดัชนีอาร์เรย์นั้นได้ ถ้าคุณลองคุณทำ UB
Johannes Schaub - litb

2
ฉันโหวตให้เปิดใหม่ ฉันโหวตให้ปิดก่อนที่จะรู้ว่ามีความแตกต่าง
GManNickG

คำตอบ:


24

ใน C ++ ที่มีอยู่ในปี 2010 โปรแกรมใด ๆ ที่ถูกต้องซึ่งใช้คีย์เวิร์ด "auto" หรือ "register" จะมีความหมายเหมือนกันกับคำหลักที่นำออก (เว้นแต่ว่าจะปรากฏในมาโครแบบสตริงหรือบริบทอื่นที่คล้ายคลึงกัน) ในแง่นั้นคำหลักไม่มีประโยชน์สำหรับการรวบรวมโปรแกรมอย่างถูกต้อง ในทางกลับกันคำหลักอาจมีประโยชน์ในบริบทมาโครบางอย่างเพื่อให้แน่ใจว่าการใช้มาโครอย่างไม่เหมาะสมจะทำให้เกิดข้อผิดพลาดเวลาคอมไพล์แทนที่จะสร้างโค้ดปลอม

ในภาษา C ++ 11 และเวอร์ชันที่ใหม่กว่าautoคีย์เวิร์ดถูกกำหนดให้ทำหน้าที่เป็นประเภทหลอกสำหรับอ็อบเจ็กต์ที่ถูกกำหนดค่าเริ่มต้นซึ่งคอมไพลเลอร์จะแทนที่โดยอัตโนมัติด้วยประเภทของนิพจน์การเริ่มต้น ดังนั้นใน C ++ 03 การประกาศ: auto int i=(unsigned char)5;เทียบเท่ากับint i=5;เมื่อใช้ภายในบริบทบล็อกและauto i=(unsigned char)5;เป็นการละเมิดข้อ จำกัด ใน C ++ 11 auto int i=(unsigned char)5;กลายเป็นการละเมิดข้อ จำกัด ในขณะที่auto i=(unsigned char)5;เทียบเท่ากับauto unsigned char i=5;.


22
ตัวอย่างของบิตสุดท้ายอาจเป็นประโยชน์
Dennis Zickefoose

14
คำตอบนี้ไม่ถูกต้องอีกต่อไปตั้งแต่ปี 2011 autoไม่สามารถละคำหลักไปได้เลย ... บางทีคุณอาจอัปเดตคำตอบของคุณได้
Walter

2
@ วอลเตอร์: คุณสามารถอ้างอิงสิ่งที่เปลี่ยนแปลงได้หรือไม่? ฉันไม่ได้ติดตามการเปลี่ยนแปลงภาษาทั้งหมด
supercat

2
@supercat ใช่ในขณะนี้ แต่registerเลิกใช้งานแล้วและจะมีข้อเสนอให้ลบออกสำหรับ C ++ 17
Jonathan Wakely

3
อ้างอิงจากen.cppreference.com/w/cpp/language/autoโพสต์ C ++ 11 autoตอนนี้ใช้สำหรับการหักประเภทอัตโนมัติ แต่ก่อนหน้านั้นมีการใช้เพื่อระบุว่าคุณต้องการให้เก็บตัวแปรของคุณ "โดยอัตโนมัติ" ( ดังนั้นฉันเดาว่าบนสแต็ก ) ตรงข้ามกับคีย์เวิร์ดregister(หมายถึง "การลงทะเบียนของโปรเซสเซอร์"):
Guillaume

96

register เป็นคำใบ้ให้กับคอมไพลเลอร์โดยแนะนำให้เก็บตัวแปรนั้นไว้ในรีจิสเตอร์โปรเซสเซอร์แทนหน่วยความจำ (ตัวอย่างเช่นแทนที่จะเป็นสแต็ก)

คอมไพเลอร์อาจปฏิบัติตามคำใบ้นั้นหรือไม่ก็ได้

อ้างอิงจาก Herb Sutter ใน"คำหลักที่ไม่ใช่ (หรือแสดงความคิดเห็นโดยใช้ชื่ออื่น)" :

ตัวระบุรีจิสเตอร์มีความหมายเหมือนกับตัวระบุอัตโนมัติ ...


2
อย่างไรก็ตามตั้งแต่ C ++ 17 จะเลิกใช้งานไม่ได้ใช้งานและสงวนไว้
ZachB

@ZachB ไม่ถูกต้อง register สงวนไว้ใน C ++ 17 แต่ยังใช้งานได้และทำงานได้เกือบจะเหมือนกับรีจิสเตอร์ของ C
Lewis Kelsey

@LewisKelsey ไม่ได้ใช้และสงวนไว้ในข้อมูลจำเพาะ C ++ 17; ไม่ใช่หนึ่งstorage-class-specifierในไวยากรณ์และไม่มีความหมายที่กำหนดไว้ คอมไพเลอร์ที่เข้ากันได้อาจทำให้เกิดข้อผิดพลาดเช่น Clang ได้ อย่างไรก็ตามการใช้งานบางอย่างยังคงอนุญาตและไม่สนใจ (MSVC, ICC) หรือใช้เป็นคำแนะนำในการเพิ่มประสิทธิภาพ (GCC) ดูopen-std.org/jtc1/sc22/wg21/docs/papers/2015/p0001r1.html ฉันพูดผิดในประเด็นหนึ่ง: มันเลิกใช้งานใน C ++ 11
ZachB


25

ด้วยคอมไพเลอร์ในปัจจุบันอาจจะไม่มีอะไร เป็นคำแนะนำดั้งเดิมในการวางตัวแปรในรีจิสเตอร์เพื่อการเข้าถึงที่เร็วขึ้น แต่คอมไพเลอร์ส่วนใหญ่ในปัจจุบันไม่สนใจคำใบ้นั้นและตัดสินใจด้วยตัวเอง


9

แทบไม่มีอะไรแน่นอน

registerเป็นคำแนะนำสำหรับคอมไพเลอร์ที่คุณวางแผนจะใช้xเป็นจำนวนมากและคุณคิดว่ามันควรจะอยู่ในรีจิสเตอร์

อย่างไรก็ตามขณะนี้คอมไพเลอร์สามารถกำหนดค่าที่ควรวางไว้ในรีจิสเตอร์ได้ดีกว่าค่าเฉลี่ยของโปรแกรมเมอร์ (หรือแม้แต่ผู้เชี่ยวชาญ) ดังนั้นคอมไพเลอร์จึงเพิกเฉยต่อคีย์เวิร์ดและทำในสิ่งที่ต้องการ



7

registerคำหลักที่เป็นประโยชน์สำหรับ:

  • การประกอบแบบอินไลน์
  • ผู้เชี่ยวชาญการเขียนโปรแกรม C / C ++
  • การประกาศตัวแปรที่แคชได้

ตัวอย่างของระบบการผลิตที่registerจำเป็นต้องใช้คีย์เวิร์ด:

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

เลิกใช้งานแล้วตั้งแต่ C ++ 11 และไม่มีการใช้งานและสงวนไว้ใน C ++ 17


2
และฉันจะเพิ่มว่าคีย์เวิร์ด 'register' จะมีประโยชน์กับไมโครคอนโทรลเลอร์ที่รันโปรแกรม C ++ ตัวเดียวโดยไม่มีเธรดและไม่มีมัลติทาสก์ โปรแกรม C ++ จะต้องเป็นเจ้าของ CPU ทั้งหมดเพื่อให้แน่ใจว่าตัวแปร 'register' จะไม่ถูกย้ายจากรีจิสเตอร์ CPU พิเศษ
Santiago Villafuerte

@SantiagoVillafuerte คุณต้องการเพิ่มแก้ไขคำตอบหรือไม่
ncomputers

ฉันไม่แน่ใจในคำตอบของฉัน ... แม้ว่ามันจะฟังดูน่าเชื่อถือก็ตาม ฉันอยากจะฝากไว้เป็นความคิดเห็นเพื่อให้คนอื่นอนุมัติหรือไม่อนุมัติ
Santiago Villafuerte

1
@SantiagoVillafuerte สิ่งนี้ไม่เป็นความจริงในระบบมัลติทาสก์เมื่อบริบทเปลี่ยน OS ไม่ใช่แอปพลิเคชันมีหน้าที่ในการบันทึก / กู้คืนการลงทะเบียน เนื่องจากคุณไม่ได้สลับบริบทหลังจากคำสั่ง CPU ทุกครั้งการใส่สิ่งต่างๆลงในรีจิสเตอร์จึงมีความหมายอย่างยิ่ง คำตอบอื่น ๆ ที่นี่ (คอมไพเลอร์นั้นไม่สนใจความคิดเห็นของคุณเมื่อต้องลงทะเบียนการจัดสรร) มีความถูกต้องมากกว่า
คิวบิก

ตัวอย่างที่คุณแสดงนั้นใช้ส่วนขยาย Explicit Register Variables ของ GCCซึ่งแตกต่างจากตัวregisterระบุคลาสพื้นที่เก็บข้อมูลและ GCC ยังรองรับ
ZachB

2

ในฐานะของ GCC 9.3 รวบรวมโดยใช้-std=c++2a, registerผลิตเตือนคอมไพเลอร์ แต่ก็ยังคงมีผลที่ต้องการและพฤติกรรมเหมือนกันกับซีregisterเมื่อรวบรวมได้โดยไม่ต้อง -O1 - ธงเพิ่มประสิทธิภาพ Ofast ในส่วนที่เกี่ยวกับเรื่องนี้คำตอบ อย่างไรก็ตามการใช้ clang ++ - 7 ทำให้เกิดข้อผิดพลาดของคอมไพเลอร์ ใช่แล้วการregisterเพิ่มประสิทธิภาพจะสร้างความแตกต่างในการคอมไพล์มาตรฐานโดยไม่มีแฟล็ก -O การปรับให้เหมาะสม แต่เป็นการเพิ่มประสิทธิภาพพื้นฐานที่คอมไพเลอร์จะคิดออกแม้จะมี -O1 ก็ตาม

ข้อแตกต่างเพียงอย่างเดียวคือใน C ++ คุณได้รับอนุญาตให้ใช้ที่อยู่ของตัวแปร register ซึ่งหมายความว่าการเพิ่มประสิทธิภาพจะเกิดขึ้นก็ต่อเมื่อคุณไม่ใช้ที่อยู่ของตัวแปรหรือนามแฝง (เพื่อสร้างตัวชี้) หรือใช้อ้างอิง ของมันในโค้ด (เฉพาะบน - O0 เนื่องจากการอ้างอิงมีแอดเดรสด้วยเนื่องจากเป็นตัวชี้ const บนสแต็กซึ่งเช่นเดียวกับตัวชี้สามารถปรับให้เหมาะสมจากสแต็กหากคอมไพล์โดยใช้ -Ofast ยกเว้นว่าจะไม่ปรากฏ บนสแต็กโดยใช้ -Ofast เนื่องจากไม่เหมือนกับตัวชี้คือไม่สามารถสร้างได้volatileและไม่สามารถนำที่อยู่ได้) มิฉะนั้นจะทำงานเหมือนที่คุณไม่ได้ใช้registerและค่าจะถูกเก็บไว้ในสแต็ก

บน -O0 ข้อแตกต่างอีกประการหนึ่งคือconst registerใน gcc C และ gcc C ++ จะไม่ทำงานเหมือนกัน ใน gcc C const registerมีลักษณะการทำงานregisterเนื่องจาก block-scope consts ไม่ได้รับการปรับให้เหมาะสมกับ gcc ในเสียงดัง C registerไม่ทำอะไรเลยและใช้เฉพาะconstการเพิ่มประสิทธิภาพขอบเขตบล็อกเท่านั้น ใน gcc C จะregisterใช้การเพิ่มประสิทธิภาพ แต่constที่ขอบเขตบล็อกไม่มีการปรับให้เหมาะสม ใน gcc C ++ การเพิ่มประสิทธิภาพทั้งสองregisterและconstขอบเขตบล็อกจะรวมกัน

#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

registerบอกรวบรวมเพื่อ 1) เก็บตัวแปรท้องถิ่นใน callee บันทึกการลงทะเบียนในกรณีนี้rbxและ 2) การเพิ่มประสิทธิภาพการเขียนสแต็คว่าที่อยู่ของตัวแปรจะไม่ดำเนินการ constบอกให้คอมไพเลอร์แทนที่ค่าทันที (แทนที่จะกำหนดรีจิสเตอร์หรือโหลดจากหน่วยความจำ) และเขียนตัวแปรโลคัลลงในสแต็กเป็นลักษณะการทำงานเริ่มต้น const registerคือการรวมกันของการเพิ่มประสิทธิภาพที่กล้าหาญเหล่านี้ นี่คือเส้นที่บางเฉียบที่สุดเท่าที่จะทำได้

นอกจากนี้ใน gcc C และ C ++ registerดูเหมือนว่าจะสร้างช่องว่าง 16 ไบต์แบบสุ่มบนสแต็กสำหรับโลคัลแรกบนสแต็กซึ่งไม่ได้เกิดขึ้นกับconst register.

การรวบรวมโดยใช้ -Ofast อย่างไรก็ตาม registerมีเอฟเฟกต์การปรับให้เหมาะสมเป็น 0 เพราะถ้าสามารถใส่ลงทะเบียนหรือทำการลงทะเบียนได้ทันทีมันจะเป็นเสมอและถ้าไม่สามารถทำได้ constยังคงเพิ่มประสิทธิภาพการโหลดบน C และ C ++ แต่ในขอบเขตไฟล์เท่านั้น ; volatileยังคงบังคับให้จัดเก็บและโหลดค่าจากสแต็ก

.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

1

พิจารณากรณีที่เครื่องมือเพิ่มประสิทธิภาพของคอมไพเลอร์มีตัวแปรสองตัวและถูกบังคับให้หกตัวลงบนสแตก มันเกิดขึ้นที่ตัวแปรทั้งสองมีน้ำหนักเท่ากันกับคอมไพเลอร์ เนื่องจากไม่มีความแตกต่างคอมไพเลอร์จะทำตัวแปรตัวใดตัวหนึ่งโดยพลการ ในทางกลับกันไฟล์registerคำหลักจะช่วยให้คอมไพเลอร์ทราบว่าตัวแปรใดจะถูกเข้าถึงบ่อยขึ้น คล้ายกับคำสั่ง x86 prefetch แต่สำหรับเครื่องมือเพิ่มประสิทธิภาพคอมไพเลอร์

เห็นได้ชัดว่าregisterคำใบ้คล้ายกับคำแนะนำความน่าจะเป็นสาขาที่ผู้ใช้ให้มาและสามารถอนุมานได้จากคำใบ้ความน่าจะเป็น ถ้าคอมไพเลอร์รู้ว่ามีการใช้ branch บ่อยครั้งมันจะเก็บตัวแปรที่เกี่ยวข้องกับ branch ไว้ในรีจิสเตอร์ registerดังนั้นผมจึงขอแนะนำให้ดูแลเพิ่มเติมเกี่ยวกับคำแนะนำสาขาและลืมเกี่ยวกับ ตามหลักการแล้วผู้สร้างโปรไฟล์ของคุณควรสื่อสารกับคอมไพเลอร์และไม่ให้คุณคิดถึงความแตกต่างดังกล่าว

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.