ปลอดภัยไหมที่จะใช้ -1 เพื่อตั้งค่าบิตทั้งหมดให้เป็นจริง


134

ฉันเคยเห็นรูปแบบนี้ใช้มากใน C & C ++

unsigned int flags = -1;  // all bits are true

นี่เป็นวิธีพกพาที่ดีในการทำสิ่งนี้หรือไม่? หรือใช้อยู่0xffffffffหรือ~0ดีกว่า?


1
ฉันไม่เข้าใจคุณช่วยอธิบายได้ไหม
corazza

8
ฉันคิดว่าความหมายของรหัสนั้นชัดเจนหรือไม่เป็นคำถามที่สำคัญที่สุด แม้ว่า-1จะใช้งานได้ตลอดเวลา แต่ความจริงที่ว่าจำเป็นต้องแสดงความคิดเห็นหลังจากนั้นแสดงว่ารหัสนั้นไม่ชัดเจน หากตัวแปรถูกกำหนดให้เป็นชุดของแฟล็กเหตุใดจึงกำหนดให้เป็นจำนวนเต็ม ประเภทของมันอาจเป็นจำนวนเต็ม แต่ไม่ใช่จำนวนเต็มตามความหมาย คุณจะไม่เพิ่มหรือคูณมัน ดังนั้นฉันจะ0xffffffffไม่ใช้เพื่อการพกพาหรือความถูกต้อง แต่เพื่อความชัดเจน
Cam Jackson

@CamJackson ความคิดเห็นไม่ใช่และใครก็ตามที่เขียนรหัส C อาจคุ้นเคยกับการแสดงค่า
เส้นทางไมล์

เดิมคำถามถูกแท็กอย่างถูกต้องเป็น C และ C ++ ภาษาอาจแตกต่างกันใน C ++ นั้นมีข้อเสนอให้ต้องการส่วนเติมเต็มสองอย่าง ที่กล่าวว่ามันไม่ได้เปลี่ยนความจริงที่ว่า-1ยังคงเป็นโซลูชันแบบพกพาและใช้งานร่วมกันได้สำหรับทั้งสองภาษา แต่อาจส่งผลต่อเหตุผลบางประการในคำตอบอื่น ๆ
Adrian McCarthy

คำตอบ:


156

ฉันขอแนะนำให้คุณทำตามที่คุณได้แสดงไว้เนื่องจากเป็นวิธีที่ตรงไปตรงมาที่สุด เริ่มต้น-1ที่จะใช้งานได้ตลอดเวลาโดยไม่ขึ้นกับการแสดงสัญลักษณ์จริงในขณะที่~บางครั้งจะมีพฤติกรรมที่น่าแปลกใจเนื่องจากคุณจะต้องมีประเภทตัวถูกดำเนินการที่ถูกต้อง จากนั้นคุณจะได้รับมูลค่าที่สูงที่สุดของunsignedประเภท

สำหรับตัวอย่างของความประหลาดใจที่เป็นไปได้ให้พิจารณาสิ่งนี้:

unsigned long a = ~0u;

ไม่จำเป็นต้องเก็บรูปแบบที่มีบิต 1 aทั้งหมด แต่มันเป็นครั้งแรกที่จะสร้างรูปแบบกับบิตทั้ง 1 แห่งในอีกด้วยและจากนั้นกำหนดให้unsigned int aจะเกิดอะไรขึ้นเมื่อunsigned longมีบิตมากขึ้นนั่นไม่ใช่ทั้งหมดที่เป็น 1

และพิจารณาสิ่งนี้ซึ่งจะล้มเหลวในการเป็นตัวแทนเสริมที่ไม่ใช่สอง:

unsigned int a = ~0; // Should have done ~0u !

เหตุผลก็คือ~0ต้องกลับบิตทั้งหมด การเปลี่ยนกลับที่จะให้ผล-1กับเครื่องเสริมของทั้งสอง (ซึ่งเป็นค่าที่เราต้องการ!) แต่จะไม่ให้ผล-1กับการแสดงอื่น บนเครื่องเติมเต็มหนึ่งเครื่องให้ผลเป็นศูนย์ ดังนั้นบนเครื่องเสริมของหนึ่งเครื่องข้างต้นจะเริ่มต้นaเป็นศูนย์

สิ่งที่คุณควรเข้าใจคือทุกอย่างเกี่ยวกับค่าไม่ใช่บิต ตัวแปรที่จะเริ่มต้นกับความคุ้มค่า หากใน initializer คุณแก้ไขบิตของตัวแปรที่ใช้สำหรับการเริ่มต้นค่าจะถูกสร้างขึ้นตามบิตเหล่านั้น ค่าที่คุณต้องการที่จะเริ่มต้นaกับค่าเป็นไปได้สูงสุดเป็นหรือ-1 UINT_MAXอย่างที่สองจะขึ้นอยู่กับประเภทของa- คุณจะต้องใช้ULONG_MAXสำหรับunsigned longไฟล์. อย่างไรก็ตามวิธีแรกจะไม่ขึ้นอยู่กับประเภทของมันและเป็นวิธีที่ดีในการได้รับมูลค่าสูงสุด

เราไม่ได้พูดถึงว่า-1มีบิตทั้งหมดหรือไม่ (ไม่ได้มีเสมอไป) และเราไม่ได้พูดถึงว่า~0มีบิตทั้งหมดหรือไม่(มีแน่นอน)

แต่สิ่งที่เรากำลังพูดถึงคือผลลัพธ์ของflagsตัวแปรเริ่มต้นคืออะไร และมันเพียง แต่-1จะทำงานร่วมกับทุกประเภทและเครื่องจักร


10
เหตุใดจึงรับประกัน -1 ว่าจะแปลงเป็นค่าทั้งหมด ได้รับการรับรองตามมาตรฐานหรือไม่?
ม.ค.

9
การแปลงที่เกิดขึ้นคือการเพิ่มมากกว่า ULONG_MAX หนึ่งครั้งซ้ำ ๆ จนกว่าจะอยู่ในช่วง (6.3.1.3 ในแบบร่าง C TC2) ใน C ++ ก็เหมือนกันเพียงแค่ใช้วิธีอื่นที่ทำให้เป็นทางการ (โมดูโล 2 ^ n) ทุกอย่างมาจากความสัมพันธ์ทางคณิตศาสตร์
Johannes Schaub - litb

6
@litb: การแคสต์ -1 เป็นวิธีที่ดีในการรับค่าที่ไม่ได้ลงนามสูงสุด แต่ก็ไม่สามารถอธิบายได้จริงๆ นั่นคือเหตุผลว่าทำไมค่าคงที่ _MAX จึงมีอยู่ (SIZE_MAX ถูกเพิ่มใน C99) ได้รับรุ่น C ++ numeric_limits<size_t>::max()ค่อนข้างยืดเยื้อ แต่นักแสดงก็เช่นกัน ...
Christoph

12
"เราไม่ได้พูดถึงว่า -1 มีบิตทั้งหมดหรือไม่ (ไม่ได้มีเสมอไป) และเราไม่ได้พูดถึงว่า ~ 0 มีบิตทั้งหมดหรือไม่ (แน่นอนว่ามี)" - ห๊า ??? ฉันคิดว่าจุดรวมคือการตั้งค่าบิตทั้งหมดเป็น 1 นั่นคือวิธีการทำงานของแฟล็ก .. ไม่ ?? คุณมองไปที่บิต ใครสนใจทักมาได้เลยค่า
mpen

14
@ มาร์คผู้ถามใส่ใจ. เขาถามว่า "ปลอดภัยไหมที่จะใช้ -1 เพื่อตั้งค่าบิตทั้งหมดให้เป็นจริง" สิ่งนี้ไม่ได้ถามเกี่ยวกับบิตที่-1แสดงโดยและไม่ถามว่าบิต~0มีอะไรบ้าง เราอาจไม่สนใจเกี่ยวกับค่าต่างๆ แต่คอมไพเลอร์ทำ เราไม่สามารถเพิกเฉยต่อความจริงที่ว่าการดำเนินการทำงานร่วมกับค่านิยม ค่าของ~0อาจจะไม่-1แต่นี้เป็นค่าที่คุณต้องการ ดูคำตอบของฉันและสรุปของ @ Dingo
Johannes Schaub - litb

51
  • unsigned int flags = -1; เป็นแบบพกพา
  • unsigned int flags = ~0; ไม่สามารถพกพาได้เนื่องจากต้องอาศัยการแสดงสองส่วนเสริม
  • unsigned int flags = 0xffffffff; ไม่สามารถพกพาได้เนื่องจากถือว่า ints 32 บิต

หากคุณต้องการตั้งค่าบิตทั้งหมดในแบบที่รับรองโดยมาตรฐาน C ให้ใช้บิตแรก


10
~ 0 (เช่นตัวดำเนินการส่วนเสริม) อาศัยการแสดงส่วนเสริมของสองอย่างไร
Drew Hall

11
คุณมีสิ่งนี้อยู่ข้างหลัง การตั้งค่าแฟล็กเป็น -1 ซึ่งขึ้นอยู่กับการแทนค่าสองส่วน ในการแสดงเครื่องหมาย + ขนาดลบหนึ่งมีเพียงสองบิตที่กำหนด: บิตเครื่องหมายและบิตที่มีนัยสำคัญน้อยที่สุดของขนาด
Stephen C. Steel

15
มาตรฐาน C กำหนดให้ค่า int ของศูนย์มีบิตเครื่องหมายและบิตค่าทั้งหมดเป็นศูนย์ หลังจากส่วนเติมเต็มแล้วบิตเหล่านั้นทั้งหมดเป็นหนึ่ง ค่าของ int พร้อมชุดบิตทั้งหมดคือ Sign-and-magnitude: INT_MIN One's complement: -0 Two's complement: -1 ดังนั้นคำสั่ง "unsigned int แฟล็ก = ~ 0;" จะกำหนดค่าใด ๆ ข้างต้นที่ตรงกับการแสดงจำนวนเต็มของแพลตฟอร์ม แต่ '-1' ของส่วนเติมเต็มทั้งสองเป็นเพียงหนึ่งเดียวที่จะตั้งค่าบิตแฟล็กทั้งหมดให้เป็นหนึ่ง
Dingo

9
@ สตีเฟน: เห็นด้วยกับการเป็นตัวแทน แต่เมื่อกำหนดค่า int ให้กับ int ที่ไม่ได้ลงนามผู้ที่ไม่ได้ลงชื่อจะไม่ได้รับค่าโดยการนำการแทนค่าภายในของค่า int มาใช้ (ยกเว้นในระบบเสริมสองระบบที่โดยทั่วไปใช้งานได้) ค่าทั้งหมดที่กำหนดให้กับ int ที่ไม่ได้ลงนามคือโมดูโล (UINT_MAX + 1) ดังนั้นการกำหนด -1 จึงใช้ได้ไม่ว่าจะเป็นการแสดงภายในก็ตาม
Dingo

20
@ มาร์ค: คุณกำลังสับสนในการดำเนินการสองอย่าง ~0แน่นอนว่าให้intค่ากับชุดบิตทั้งหมด แต่การกำหนด an intให้unsigned intไม่จำเป็นต้องส่งผลให้ int ที่ไม่ได้ลงนามมีรูปแบบบิตเดียวกับรูปแบบบิตที่ลงนาม เฉพาะการแสดงส่วนเสริมของ 2 เท่านั้นที่เป็นเช่นนี้เสมอ ในการแสดงส่วนเติมเต็มหรือขนาดเครื่องหมาย 1 วินาทีให้กำหนดintค่าลบให้กับunsigned intผลลัพธ์ในรูปแบบบิตอื่น เนื่องจากมาตรฐาน C ++ กำหนดให้การแปลงที่ลงชื่อ -> ไม่ได้ลงชื่อเป็นค่าโมดูโลเท่ากันไม่ใช่ค่าที่มีบิตเดียวกัน
Steve Jessop

25

ตรงไปตรงมาฉันคิดว่า fff ทั้งหมดอ่านได้มากกว่า สำหรับความคิดเห็นที่ว่ามันเป็นแอนติแพตเทิร์นถ้าคุณสนใจจริงๆว่าบิตทั้งหมดถูกตั้งค่า / เคลียร์ฉันขอยืนยันว่าคุณอาจอยู่ในสถานการณ์ที่คุณสนใจขนาดของตัวแปรอยู่แล้วซึ่งจะเรียกร้องให้มีบางอย่างเช่นบูสต์ :: uint16_t ฯลฯ


มีหลายกรณีที่คุณไม่สนใจมากนัก แต่ก็หายาก เช่นอัลกอริทึมที่ทำงานกับชุดข้อมูลของ N บิตโดยการแยกย่อยออกเป็นชิ้นส่วนขนาดของ (ไม่ได้ลงชื่อ) * CHAR_BIT บิตแต่ละชุด
MSalters

2
+1. แม้ว่าขนาดของประเภทข้อมูลจะใหญ่กว่า # ของ F (กล่าวคือคุณไม่ได้ตั้งค่าบิตทั้งหมดให้เป็นจริง) เนื่องจากคุณตั้งค่าอย่างชัดเจนอย่างชัดเจนอย่างน้อยคุณก็ทราบว่าบิตใด "ปลอดภัย ใช้งาน "..
mpen

17

วิธีที่หลีกเลี่ยงปัญหาที่กล่าวถึงคือทำ:

unsigned int flags = 0;
flags = ~flags;

พกพาได้และตรงประเด็น


2
แต่แล้วคุณสูญเสียความสามารถในการประกาศเป็นflags const
David Stone

1
@DavidStoneunsigned int const flags = ~0u;

@Zoidberg '- มันล้มเหลวในการทำงานบนระบบอื่นที่ไม่ใช่ส่วนเสริมของสองระบบ ตัวอย่างเช่นในระบบ Sign-Magnitude ~0คือจำนวนเต็มที่มีการตั้งค่าบิตทั้งหมดเป็น 1 แต่เมื่อคุณกำหนดสิ่งนั้นintให้กับunsignedตัวแปรflagsคุณจะทำการแปลงค่าจาก-2**31(สมมติว่าเป็น 32 บิตint) (-2**31 % 2**32) == 2**31ซึ่งเป็นจำนวนเต็ม ด้วยบิตทั้งหมด แต่ชุดแรกเป็น 1
David Stone

นั่นเป็นเหตุผลว่าทำไมคำตอบนี้จึงเป็นอันตราย ดูเหมือนว่าคำตอบของ @Zoidberg จะเหมือนกัน แต่จริงๆแล้วมันไม่ใช่ อย่างไรก็ตามในฐานะคนที่อ่านโค้ดฉันจะต้องคิดทบทวนเพื่อทำความเข้าใจว่าเหตุใดคุณจึงเริ่มต้นใช้งานสองขั้นตอนและอาจถูกล่อลวงให้เปลี่ยนเป็นขั้นตอนเดียว
David Stone

2
อ่าใช่ฉันไม่สังเกตเห็นuคำต่อท้ายในคำตอบของคุณ แน่นอนว่าจะใช้งานได้ แต่ยังคงมีปัญหาในการระบุประเภทข้อมูลที่คุณใช้ ( unsignedและไม่ใหญ่กว่า) สองครั้งซึ่งอาจนำไปสู่ข้อผิดพลาด ข้อผิดพลาดมักจะปรากฏขึ้นหากการกำหนดและการประกาศตัวแปรเริ่มต้นอยู่ห่างกันมากขึ้น
David Stone

13

ฉันไม่แน่ใจว่าการใช้ int ที่ไม่ได้ลงชื่อสำหรับแฟล็กเป็นความคิดที่ดีตั้งแต่แรกใน C ++ แล้วบิตเซ็ตล่ะ?

std::numeric_limit<unsigned int>::max()จะดีกว่าเพราะ0xffffffffสมมติว่า int ที่ไม่ได้ลงชื่อเป็นจำนวนเต็ม 32 บิต


ฉันชอบสิ่งนี้เพราะมันเป็นมาตรฐาน แต่มันใช้คำหยาบเกินไปและทำให้คุณระบุประเภทสองครั้ง การใช้ ~ 0 น่าจะปลอดภัยกว่าเนื่องจาก 0 สามารถเป็นจำนวนเต็มประเภทใดก็ได้ (แม้ว่าฉันจะรู้ว่ามันมีกลิ่นของ C. มากเกินไป)
Macke

ความจริงมันเป็นคำพูดสามารถมองได้ว่าเป็นข้อดี แต่ฉันก็ชอบ ~ 0 เช่นกัน
Edouard อ

2
คุณสามารถลดความซับซ้อนได้ด้วยมาโคร UINT_MAX มาตรฐานเนื่องจากคุณกำลังฮาร์ดโค้ดประเภท int ที่ไม่ได้ลงนาม

2
@Macke คุณสามารถหลีกเลี่ยงการระบุประเภทใน C ++ 11 ด้วยauto. auto const flags = std::numeric_limit<unsigned>::max().
David Stone

11
unsigned int flags = -1;  // all bits are true

"นี่เป็น [,] วิธีพกพาที่ดีในการทำให้สำเร็จหรือไม่"

พกพา? ครับ .

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

นอกจากนี้วิธีการนี้มีแนวโน้มที่จะมีคำเตือนคอมไพเลอร์ หากต้องการยกเลิกคำเตือนโดยไม่ทำให้คอมไพเลอร์ของคุณเสียคุณจะต้องมีการแคสต์ที่ชัดเจน ตัวอย่างเช่น,

unsigned int flags = static_cast<unsigned int>(-1);

นักแสดงที่ชัดเจนต้องการให้คุณใส่ใจกับประเภทเป้าหมาย หากคุณให้ความสนใจกับประเภทเป้าหมายคุณจะหลีกเลี่ยงข้อผิดพลาดของแนวทางอื่น ๆ

คำแนะนำของฉันคือให้ความสนใจกับประเภทเป้าหมายและตรวจสอบให้แน่ใจว่าไม่มีการแปลงโดยนัย ตัวอย่างเช่น:

unsigned int flags1 = UINT_MAX;
unsigned int flags2 = ~static_cast<unsigned int>(0);
unsigned long flags3 = ULONG_MAX;
unsigned long flags4 = ~static_cast<unsigned long>(0);

ซึ่งทั้งหมดนี้ถูกต้องและชัดเจนมากขึ้นสำหรับเพื่อนโปรแกรมเมอร์ของคุณ

และด้วย C ++ 11 : เราสามารถใช้autoเพื่อทำให้สิ่งเหล่านี้ง่ายขึ้น:

auto flags1 = UINT_MAX;
auto flags2 = ~static_cast<unsigned int>(0);
auto flags3 = ULONG_MAX;
auto flags4 = ~static_cast<unsigned long>(0);

ฉันคิดว่าถูกต้องและชัดเจนดีกว่าถูกต้อง


10

การแปลง -1 เป็นประเภทที่ไม่ได้ลงชื่อรับรองโดยมาตรฐานเพื่อให้ได้ผลลัพธ์ทั้งหมด ใช้~0Uไม่ดีโดยทั่วไปตั้งแต่0มีประเภทและจะไม่กรอกบิตทั้งหมดของประเภทที่ไม่ได้ลงชื่อขนาดใหญ่เว้นแต่สิ่งที่คุณเขียนอย่างชัดเจนเช่นunsigned int ~0ULLในระบบที่มีสติ~0ควรจะเหมือนกัน-1แต่เนื่องจากมาตรฐานอนุญาตให้ใช้แทนตัวเสริมและสัญลักษณ์ / ขนาดได้ดังนั้นจึงไม่สามารถพกพาได้

แน่นอนว่าการเขียนออกมาเป็นเรื่องปกติ0xffffffffหากคุณรู้ว่าคุณต้องการ 32 บิต แต่ -1 มีข้อได้เปรียบที่จะใช้งานได้ในบริบทใด ๆ แม้ว่าคุณจะไม่ทราบขนาดของประเภทก็ตามเช่นมาโครที่ทำงานได้หลายประเภท หรือหากขนาดของประเภทแตกต่างกันไปตามการใช้งาน ถ้าคุณทำทราบชนิดอีกวิธีที่ปลอดภัยที่จะได้รับทุกคนที่เป็นแมโครขีด จำกัดUINT_MAX, ULONG_MAX, ULLONG_MAXฯลฯ

โดยส่วนตัวฉันมักจะใช้ -1 มันใช้งานได้เสมอและคุณไม่ต้องคิดถึงมัน


FWIW ถ้าฉันหมายถึง "ทั้งหมด 1 บิต" ที่ฉันใช้~(type)0( typeแน่นอนว่าต้องกรอกด้านขวา) การแคสต์ศูนย์ยังคงให้ผลลัพธ์เป็นศูนย์ดังนั้นจึงชัดเจนและการลบบิตทั้งหมดในประเภทเป้าหมายนั้นมีการกำหนดไว้อย่างชัดเจน ไม่บ่อยนักที่ฉันต้องการการดำเนินการนั้นจริงๆ YMMV.
Donal Fellows

6
@ Donal: คุณคิดผิด C ระบุว่าเมื่อแปลงค่าที่ไม่พอดีกับประเภทที่ไม่ได้ลงนามค่าจะลดลงโมดูโล 2 ^ n โดยที่ n คือจำนวนบิตในประเภทปลายทาง สิ่งนี้ใช้ได้ทั้งกับค่าที่ลงนามและประเภทที่ไม่ได้ลงชื่อที่มีขนาดใหญ่ มันไม่มีอะไรเกี่ยวข้องกับส่วนเสริม twos
R .. GitHub STOP HELPING ICE

2
พวกเขาทำใช้มันเหมือนกันซึ่งเป็นที่น่ารำคาญ; คุณเพียงแค่ใช้ตัวเลือกการลบที่ไม่ได้ลงชื่อแทนการลงนามหรือnegคำสั่ง เครื่องจักรที่มีพฤติกรรมการคำนวณทางคณิตศาสตร์ที่ลงนามปลอมจะมีรหัส opcodes เลขคณิตที่ลงชื่อ / ไม่ได้ลงนามแยกกัน แน่นอนว่าคอมไพเลอร์ที่ดีจริงๆมักจะเพิกเฉยต่อ opcodes ที่ลงชื่อไว้เสมอแม้กระทั่งสำหรับค่าที่ลงชื่อแล้วและด้วยเหตุนี้จึงได้รับ twos-complement ฟรี
R .. GitHub STOP HELPING ICE

1
@R .: var = ~(0*var)กรณีที่จะล้มเหลวสำหรับการเป็นแคบชนิดที่ไม่ได้ลงชื่อกว่าvar intบางทีvar = ~(0U*var)? (โดยส่วนตัวฉันยังชอบ-1มากกว่า)
คาเฟ่

1
คำตอบนี้ชัดเจนกว่าของ Johannes Schaub มาก อย่างไรก็ตามการกำหนดจำนวนเต็มลิเทอรัลที่เป็นลบให้กับประเภทที่ไม่ได้ลงชื่อโดยไม่มีการแคสต์มักจะส่งผลให้คอมไพเลอร์เตือน การระงับคำเตือนนั้นจำเป็นต้องใช้นักแสดงซึ่งหมายความว่าคุณต้องใส่ใจกับประเภทเป้าหมายอยู่แล้วดังนั้นคุณอาจใช้ UINT_MAX หรือ ULONG_MAX ได้เช่นกันและมีความชัดเจนแทนที่จะใช้รายละเอียดเล็กน้อยในมาตรฐานซึ่งจะทำให้เพื่อนโปรแกรมเมอร์หลายคนสับสนอย่างชัดเจน .
Adrian McCarthy

5

ตราบใดที่คุณมี#include <limits.h>หนึ่งในไฟล์รวมของคุณคุณควรใช้

unsigned int flags = UINT_MAX;

หากคุณต้องการบิตที่มีมูลค่ายาวคุณสามารถใช้

unsigned long flags = ULONG_MAX;

ค่าเหล่านี้รับประกันได้ว่าจะมีการกำหนดบิตค่าทั้งหมดของผลลัพธ์เป็น 1 ไม่ว่าจะใช้เลขจำนวนเต็มที่เซ็นชื่ออย่างไร


1
ค่าคงที่ที่คุณแนะนำนั้นถูกกำหนดไว้ในลิมิตจริงๆ h - stdint.h มีขีด จำกัด สำหรับประเภทจำนวนเต็มเพิ่มเติม (จำนวนเต็มขนาดคงที่, intptr_t, ... )
Christoph

5

ใช่. ตามที่กล่าวไว้ในคำตอบอื่น ๆ-1เป็นแบบพกพามากที่สุด อย่างไรก็ตามมันไม่ได้มีความหมายมากนัก

ในการแก้ปัญหาเหล่านี้ลองใช้ตัวช่วยง่ายๆนี้:

static const struct All1s
{
    template<typename UnsignedType>
    inline operator UnsignedType(void) const
    {
        static_assert(std::is_unsigned<UnsignedType>::value, "This is designed only for unsigned types");
        return static_cast<UnsignedType>(-1);
    }
} ALL_BITS_TRUE;

การใช้งาน:

unsigned a = ALL_BITS_TRUE;
uint8_t  b = ALL_BITS_TRUE;
uint16_t c = ALL_BITS_TRUE;
uint32_t d = ALL_BITS_TRUE;
uint64_t e = ALL_BITS_TRUE;

4
ในฐานะโปรแกรมเมอร์ C รหัสแบบนี้ทำให้ฉันฝันร้ายในตอนกลางคืน
jforberg

และสิ่งที่เกิดขึ้นเมื่อคุณใช้วิธีนี้ในบริบทเช่นALL_BITS_TRUE ^ aที่aเป็นจำนวนเต็มลงนาม? ประเภทยังคงเป็นเลขจำนวนเต็มและรูปแบบบิต (การแสดงวัตถุ) ขึ้นอยู่กับเป้าหมายว่าเป็นส่วนเสริมของ 2 หรือไม่
Peter Cordes

ไม่ALL_BITS_TRUE ^ aให้ข้อผิดพลาดในการคอมไพล์เนื่องจากALL_BITS_TRUEไม่ชัดเจน สามารถใช้งานได้เช่นuint32_t(ALL_BITS_TRUE) ^ aไร คุณสามารถลองด้วยตัวคุณเองในcpp.sh :) ปัจจุบันฉันต้องการเพิ่มstatic_assert(std::is_unsigned<UnsignedType>::value, "This is designed only for unsigned types");ในเพื่อให้แน่ใจว่าผู้ใช้ไม่ได้พยายามที่จะใช้operator int(ALL_BITS_TRUE)ฉันจะอัปเดตคำตอบ
Diamond Python

3

ฉันจะไม่ทำสิ่งที่ -1 มันค่อนข้างไม่ใช้งานง่าย (อย่างน้อยสำหรับฉัน) การกำหนดข้อมูลที่เซ็นชื่อให้กับตัวแปรที่ไม่ได้ลงชื่อดูเหมือนจะเป็นการละเมิดลำดับตามธรรมชาติของสิ่งต่างๆ

0xFFFFในสถานการณ์ของคุณฉันมักจะใช้ (ใช้ Fs ให้ถูกต้องสำหรับขนาดตัวแปร)

[BTW ฉันไม่ค่อยเห็นการหลอก -1 ในโค้ดจริง]

นอกจากนี้หากคุณจริงๆดูแลเกี่ยวกับแต่ละบิตใน vairable มันจะเป็นความคิดที่ดีที่จะเริ่มใช้ความกว้างคงที่uint8_t, uint16_t, uint32_tประเภท


2

บนโปรเซสเซอร์ IA-32 ของ Intel สามารถเขียน 0xFFFFFFFF ลงในรีจิสเตอร์ 64 บิตและได้ผลลัพธ์ที่คาดหวัง เนื่องจาก IA32e (ส่วนขยาย 64 บิตไปยัง IA32) รองรับเฉพาะ 32 บิตทันที ในคำแนะนำ 64 บิตทันที 32 บิตจะขยายการลงชื่อเป็น 64 บิต

สิ่งต่อไปนี้ผิดกฎหมาย:

mov rax, 0ffffffffffffffffh

ต่อไปนี้ทำให้ 64 1s ใน RAX:

mov rax, 0ffffffffh

เพื่อความสมบูรณ์ข้อมูลต่อไปนี้ใส่ 32 1 ในส่วนล่างของ RAX (aka EAX):

mov eax, 0ffffffffh

และอันที่จริงฉันเคยมีโปรแกรมล้มเหลวเมื่อฉันต้องการเขียน 0xffffffff ไปยังตัวแปร 64 บิตและฉันได้รับ 0xffffffffffffffffff แทน ใน C นี่จะเป็น:

uint64_t x;
x = UINT64_C(0xffffffff)
printf("x is %"PRIx64"\n", x);

ผลลัพธ์คือ:

x is 0xffffffffffffffff

ฉันคิดว่าจะโพสต์สิ่งนี้เป็นความคิดเห็นสำหรับคำตอบทั้งหมดที่บอกว่า 0xFFFFFFFF ถือว่า 32 บิต แต่หลายคนตอบว่าฉันคิดว่าฉันจะเพิ่มเป็นคำตอบแยกต่างหาก


1
ขอแสดงความยินดีคุณพบบั๊กของคอมไพเลอร์แล้ว!
tc.

สิ่งนี้ได้รับการบันทึกว่าเป็นจุดบกพร่องหรือไม่?
Nathan Fellman

1
สมมติว่าUINT64_C(0xffffffff)ขยายไปยังสิ่งที่ต้องการ0xffffffffuLLเป็นบั๊กของคอมไพเลอร์ มาตรฐาน C กล่าวถึงค่าเป็นส่วนใหญ่ค่าที่แสดง0xffffffffคือ 4294967295 (ไม่ใช่ 36893488147419103231) และไม่มีการแปลงประเภทจำนวนเต็มที่มีการเซ็นชื่อในสายตา
tc.

2

ดูคำตอบของ litb สำหรับคำอธิบายที่ชัดเจนเกี่ยวกับปัญหา

ความไม่เห็นด้วยของฉันคือพูดอย่างเคร่งครัดไม่มีหลักประกันสำหรับทั้งสองกรณี ฉันไม่รู้สถาปัตยกรรมใด ๆ ที่ไม่ได้แสดงถึงค่าที่ไม่ได้ลงนามของ 'หนึ่งน้อยกว่าสองยกกำลังของจำนวนบิต' ตามที่บิตทั้งหมดกำหนดไว้ แต่นี่คือสิ่งที่มาตรฐานกล่าวไว้จริง ๆ (3.9.1 / 7 บวก หมายเหตุ 44):

การแสดงประเภทอินทิกรัลต้องกำหนดค่าโดยใช้ระบบเลขฐานสองบริสุทธิ์ [หมายเหตุ 44:] การแสดงตำแหน่งสำหรับจำนวนเต็มที่ใช้เลขฐานสอง 0 และ 1 ซึ่งค่าที่แทนด้วยบิตต่อเนื่องเป็นส่วนเสริมเริ่มต้นด้วย 1 และคูณด้วยกำลังอินทิกรัลที่ต่อเนื่องกันของ 2 ยกเว้นบิตที่มี ตำแหน่งสูงสุด

นั่นทำให้ความเป็นไปได้ที่หนึ่งในบิตจะเป็นอะไรก็ได้


โดยทั่วไปเราไม่สามารถแน่ใจเกี่ยวกับค่าของบิตช่องว่างภายใน และถ้าเราต้องการเราก็อาจตกอยู่ในอันตรายเนื่องจากเราสามารถสร้างกับดักแทนพวกมันได้ (และมันอาจส่งสัญญาณ) อย่างไรก็ตาม std ต้องการ char ที่ไม่ได้ลงนามเพื่อให้ไม่มีบิตช่องว่างภายในและใน 4.7 / 2 ในมาตรฐาน c ++ กล่าวว่าการแปลงจำนวนเต็มเป็นชนิดที่ไม่ได้ลงนามค่าของตัวแปรที่ไม่ได้ลงนามที่เป็นผลลัพธ์คือค่าที่น้อยที่สุดที่สอดคล้องกับค่าจำนวนเต็มต้นทาง (โมดูโล 2 ^ n, n == จำนวนบิตในประเภทที่ไม่ได้ลงชื่อ) แล้ว (-1) == ((2 ^ n) -1) (mod 2 ^ n) 2 ^ n-1 มีบิตทั้งหมดที่ตั้งอยู่ในระบบเลขฐานสองที่แท้จริง
Johannes Schaub - litb

ถ้าเราจริงๆต้องการที่จะมีบิตทั้ง 1 แห่งในการเป็นตัวแทนของวัตถุชนิดที่ไม่ได้ลงชื่อเราจะต้อง memset แต่เราสามารถสร้างการแสดงกับดักได้ด้วยเหตุนี้ :( อย่างไรก็ตามการนำไปใช้งานอาจไม่มีเหตุผลที่จะทิ้งจำนวนเต็มที่ไม่ได้ลงชื่อออกไปเล็กน้อยดังนั้นจึงจะใช้มันเพื่อเก็บค่าของมัน แต่คุณมีจุดที่ดีมาก - ไม่มีอะไรหยุด การตีความจากการมีบิตไร้สาระโง่ ๆ ฉันคิดว่า (นอกเหนือจากถ่าน / ถ่านที่ลงนาม / ถ่านที่ไม่ได้ลงนามซึ่งต้องไม่มี) แน่นอน +1 :)
Johannes Schaub - litb

ในท้ายที่สุดฉันคิดว่ามาตรฐานอาจชัดเจนขึ้นว่าหมายถึงอะไรใน 4.7 / 2 ถ้ามันหมายถึงการแสดงวัตถุก็จะไม่มีที่สำหรับ padding bits อีกต่อไป (ฉันเคยเห็นคนเถียงแบบนั้นซึ่งฉันไม่เห็นว่ามีอะไรผิดปกติ) แต่ฉันคิดว่ามันพูดถึงการแทนค่า (เพราะทุกอย่างใน 4.7 / 2 นั้นเกี่ยวกับค่าอยู่แล้ว - จากนั้นช่องว่างภายในบิตอาจซ้อนอยู่ถัดจากบิตค่า
Johannes Schaub - litb

1
มาตรฐานดูเหมือนจะค่อนข้างชัดเจนว่ามีการแทนค่า '2', 1, 1's complement และ signitude 'อยู่ในใจ แต่ไม่ต้องการตัดทอนอะไรออกไป จุดที่น่าสนใจเกี่ยวกับการแสดงกับดักด้วย เท่าที่ฉันสามารถบอกได้บิตที่ฉันยกมาคือคำจำกัดความของ 'ระบบเลขฐานสองบริสุทธิ์' เท่าที่มาตรฐานเกี่ยวข้อง - บิต 'ยกเว้น' ในตอนท้ายเป็นข้อสงสัยเพียงอย่างเดียวของฉันว่ารับประกันการหล่อ -1 หรือไม่ งาน.
James Hopkin

2

แม้ว่า0xFFFF(หรือ0xFFFFFFFFฯลฯ ) อาจอ่านง่ายกว่า แต่ก็สามารถทำลายความสามารถในการพกพาในรหัสซึ่งจะพกพาได้ ตัวอย่างเช่นพิจารณารูทีนไลบรารีเพื่อนับจำนวนรายการในโครงสร้างข้อมูลที่มีการตั้งค่าบิตไว้ (บิตที่ถูกระบุโดยผู้เรียก) รูทีนอาจไม่เชื่อเรื่องพระเจ้าโดยสิ้นเชิงกับสิ่งที่บิตเป็นตัวแทน แต่ยังคงต้องมีค่าคงที่ "all bits set" ในกรณีเช่นนี้ -1 จะดีกว่าค่าคงที่ฐานสิบหกอย่างมากเนื่องจากจะใช้ได้กับขนาดบิตใด ๆ

ความเป็นไปได้อื่น ๆ หากใช้typedefค่าสำหรับ bitmask จะใช้ ~ (bitMaskType) 0; ถ้า bitmask เกิดขึ้นเป็นเพียงชนิด 16 บิตการแสดงออกว่าจะมี 16 บิตตั้ง (แม้ว่า 'int' มิฉะนั้นจะ 32 บิต) แต่ตั้งแต่ 16 บิตจะเป็นสิ่งที่จำเป็นต้องมีสิ่งที่ควรได้รับการปรับให้ไว้ที่หนึ่ง ใช้ประเภทที่เหมาะสมในตัวพิมพ์

อนึ่งนิพจน์ของแบบฟอร์มlongvar &= ~[hex_constant]มี gotcha ที่น่ารังเกียจหากค่าคงที่ฐานสิบหกมีขนาดใหญ่เกินไปที่จะใส่ใน an intแต่จะพอดีกับunsigned int. ถ้าintเป็น 16 บิตแล้วlongvar &= ~0x4000;หรือlongvar &= ~0x10000; จะล้างหนึ่งบิตlongvarแต่longvar &= ~0x8000;จะล้างบิต 15 และบิตทั้งหมดที่อยู่เหนือนั้น ค่าที่พอดีintจะมีตัวดำเนินการส่วนเสริมนำไปใช้กับประเภทintแต่ผลลัพธ์จะถูกขยายไปยังlongการตั้งค่าบิตด้านบน ค่าที่มีขนาดใหญ่เกินไปสำหรับจะมีผู้ประกอบการที่สมบูรณ์นำไปใช้กับประเภทunsigned int longอย่างไรก็ตามค่าที่อยู่ระหว่างขนาดเหล่านั้นจะใช้ตัวดำเนินการส่วนเสริมในการพิมพ์unsigned intซึ่งจะถูกแปลงเป็นประเภทlongโดยไม่มีส่วนขยายเครื่องหมาย


1

ในทางปฏิบัติ: ใช่

ในทางทฤษฎี: ไม่

-1 = 0xFFFFFFFF (หรือขนาดใดก็ตามที่ int อยู่บนแพลตฟอร์มของคุณ) เป็นจริงเฉพาะกับเลขคณิตเสริมสองตัว ในทางปฏิบัติมันจะใช้งานได้ แต่มีเครื่องดั้งเดิมอยู่ที่นั่น (เมนเฟรมของ IBM ฯลฯ ) ที่คุณมีบิตเซ็นจริงแทนที่จะเป็นตัวแทนเสริมของสอง โซลูชัน ~ 0 ที่คุณเสนอควรใช้ได้ทุกที่


6
ผมก็ว่าเหมือนกัน แต่แล้วฉันก็รู้ว่าฉันคิดผิดเนื่องจาก -1 ที่ลงชื่อจะแปลงเป็น max_value ไม่ได้ลงนามภายใต้กฎการแปลงเสมอโดยไม่คำนึงถึงการแทนค่า อย่างน้อยก็ทำใน C ++ ฉันไม่มีมาตรฐาน C ในมือ
Steve Jessop

6
มีความคลุมเครือ -1 ไม่ใช่ 0xFFFFFFFF แต่ -1 คือ 0xFFFFFFFF หากแปลงเป็น int ที่ไม่ได้ลงนาม (มี 32 บิต) นั่นคือสิ่งที่ทำให้การสนทนานี้ยากมากที่ฉันคิด หลายคนมีความคิดที่แตกต่างกันมากเมื่อพูดถึงบิตสตริงเหล่านั้น
Johannes Schaub - litb

1

ดังที่คนอื่น ๆ กล่าวไว้ -1 เป็นวิธีที่ถูกต้องในการสร้างจำนวนเต็มซึ่งจะแปลงเป็นชนิดที่ไม่ได้ลงชื่อโดยตั้งค่าบิตทั้งหมดเป็น 1 อย่างไรก็ตามสิ่งที่สำคัญที่สุดใน C ++ คือการใช้ประเภทที่ถูกต้อง ดังนั้นคำตอบที่ถูกต้องสำหรับปัญหาของคุณ (ซึ่งรวมถึงคำตอบสำหรับคำถามที่คุณถาม) คือ:

std::bitset<32> const flags(-1);

สิ่งนี้จะประกอบด้วยจำนวนบิตที่คุณต้องการเสมอ สร้าง a std::bitsetโดยตั้งค่าบิตทั้งหมดเป็น 1 ด้วยเหตุผลเดียวกันกับที่กล่าวถึงในคำตอบอื่น ๆ


0

มันปลอดภัยอย่างแน่นอนเนื่องจาก -1 จะมีการตั้งค่าบิตทั้งหมดไว้เสมอ แต่ฉันชอบ ~ 0 ดีกว่า -1 ไม่สมเหตุสมผลสำหรับunsigned intไฟล์. 0xFF... ไม่ดีเพราะขึ้นอยู่กับความกว้างของชนิด


4
"0xFF ... ไม่ดีเพราะขึ้นอยู่กับความกว้างของประเภท" ซึ่งเป็นวิธีเดียวที่จะไปได้ดี คุณควรกำหนดให้ชัดเจนว่าแต่ละแฟล็ก / บิตหมายถึงอะไรในโปรแกรมของคุณ ดังนั้นหากคุณกำหนดว่าคุณกำลังใช้ 32 บิตต่ำสุดในการจัดเก็บแฟล็กคุณควร จำกัด ตัวเองให้ใช้ 32 บิตเหล่านั้นไม่ว่าขนาดจริงของ int จะเป็น 32 หรือ 64 ก็ตาม
Juan Pablo Califano

0

ฉันพูด:

int x;
memset(&x, 0xFF, sizeof(int));

สิ่งนี้จะให้ผลลัพธ์ที่ต้องการเสมอ


4
ไม่อยู่ในระบบที่มีตัวอักษร 9 บิต!
tc.

0

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

การกำหนด -1 ใช้ได้กับประเภทจำนวนเต็มที่ไม่ได้ลงชื่อ (int ไม่ได้ลงชื่อ, uint8_t, uint16_t ฯลฯ ) สำหรับทั้ง C และ C ++

อีกทางเลือกหนึ่งสำหรับ C ++ คุณสามารถ:

  1. รวม<limits>และใช้std::numeric_limits< your_type >::max()
  2. เขียนฟังก์ชันเทมเพลตที่กำหนดเอง (ซึ่งจะช่วยให้สามารถตรวจสอบความถูกต้องได้เช่นหากประเภทปลายทางเป็นประเภทที่ไม่ได้ลงนามจริงๆ)

จุดประสงค์อาจเพิ่มความชัดเจนมากขึ้นเนื่องจากการมอบหมาย-1จะต้องมีความคิดเห็นที่อธิบายได้เสมอ


0

วิธีที่จะทำให้ความหมายชัดเจนขึ้นและยังหลีกเลี่ยงการพิมพ์ซ้ำ:

const auto flags = static_cast<unsigned int>(-1);

-6

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

ตัวอย่างเช่นในเครื่องส่วนใหญ่จำนวนเต็มคือ 2 ไบต์ = 16 บิตค่าสูงสุดที่สามารถเก็บได้คือ 2 ^ 16-1 = 65535 2 ^ 16 = 65536

0% 65536 = 0 -1% 65536 = 65535 ซึ่งตอบสนองต่อ 1111 ............. 1 และบิตทั้งหมดถูกตั้งค่าเป็น 1 (ถ้าเราพิจารณาคลาสตกค้าง mod 65536) ดังนั้นจึงมีมาก ตรงไปตรงมา

ฉันคิดว่า

ไม่ถ้าคุณคิดว่าแนวคิดนี้เป็นอาหารที่สมบูรณ์แบบสำหรับ ints ที่ไม่ได้ลงนามและใช้งานได้จริง

เพียงตรวจสอบส่วนของโปรแกรมต่อไปนี้

int หลัก () {

unsigned int a=2;

cout<<(unsigned int)pow(double(a),double(sizeof(a)*8));

unsigned int b=-1;

cout<<"\n"<<b;

getchar();

return 0;

}

คำตอบสำหรับ b = 4294967295 whcih คือ -1% 2 ^ 32 สำหรับจำนวนเต็ม 4 ไบต์

ดังนั้นจึงถูกต้องสมบูรณ์สำหรับจำนวนเต็มที่ไม่ได้ลงนาม

ในกรณีที่มีความคลาดเคลื่อนโปรดรายงาน


9
สองความคิดเห็น: ประการแรกคุณคิดผิดเกี่ยวกับขนาดของจำนวนเต็มในเครื่อง "ส่วนใหญ่" ประการที่สอง ur txt iz ยากมาก 2 อ่านเนื่องจาก 2 ur เรารวมชนิดของภาษา seckrit Plz เราภาษาอังกฤษธรรมดา
Konrad Rudolph
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.