การเพิ่มตัวชี้ไปยังอาร์เรย์ไดนามิกขนาด 0 ไม่ได้กำหนดไว้หรือไม่


34

AFAIK แม้ว่าเราจะไม่สามารถสร้างอาร์เรย์หน่วยความจำคงที่ขนาด 0 แต่เราสามารถทำได้ด้วยไดนามิก:

int a[0]{}; // Compile-time error
int* p = new int[0]; // Is well-defined

อย่างที่ฉันได้อ่านpทำหน้าที่เหมือนองค์ประกอบสุดท้ายที่จบแล้ว ฉันสามารถพิมพ์ที่อยู่ที่pชี้ไปได้

if(p)
    cout << p << endl;
  • แม้ว่าฉันจะแน่ใจว่าเราไม่สามารถอ้างถึงตัวชี้ (องค์ประกอบสุดท้ายที่ผ่านมา) เนื่องจากเราไม่สามารถใช้ตัววนซ้ำ (องค์ประกอบที่ผ่านมา) แต่สิ่งที่ฉันไม่แน่ใจคือการเพิ่มตัวชี้นั้นpหรือไม่ พฤติกรรมที่ไม่ได้กำหนด (UB) เหมือนกับตัววนซ้ำหรือไม่

    p++; // UB?

4
UB "... สถานการณ์อื่น ๆ (นั่นคือความพยายามในการสร้างตัวชี้ที่ไม่ได้ชี้ไปที่องค์ประกอบของอาเรย์เดียวกันหรือในอดีตที่ผ่านมา) ก่อให้เกิดพฤติกรรมที่ไม่ได้กำหนด .... "จาก: en.cppreference.com / w / cpp / language / operator_arithmetic
Richard Critten

3
นี่ก็คล้ายกับ a ที่std::vectorมี 0 รายการอยู่ในนั้น begin()เท่ากับแล้วend()ดังนั้นคุณไม่สามารถเพิ่มตัววนซ้ำที่ชี้ไปที่จุดเริ่มต้น
Phil1970

1
@PeterMortensen ฉันคิดว่าการแก้ไขของคุณเปลี่ยนความหมายของประโยคสุดท้าย ("สิ่งที่ฉันแน่ใจ -> ฉันไม่แน่ใจว่าทำไม") คุณช่วยกรุณาตรวจสอบอีกครั้งได้ไหม
ฟาบิโอพูดว่า Reinstate Monica

@PeterMortensen: ย่อหน้าสุดท้ายที่คุณแก้ไขได้กลายเป็นน้อยอ่านง่าย
Itachi Uchiwa

คำตอบ:


32

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

สำหรับอาเรย์ขนาด 0 ของคุณpชี้ไปหนึ่งอันสุดท้ายแล้วดังนั้นจึงไม่อนุญาตการเพิ่ม

ดู C ++ 17 8.7 / 4 เกี่ยวกับ+ผู้ประกอบการ ( ++มีข้อ จำกัด เหมือนกัน):

f การแสดงออกPชี้ไปที่องค์ประกอบx[i]ของวัตถุอาร์เรย์ที่xมีองค์ประกอบ n การแสดงออกP + JและJ + P(ที่Jมีค่าj) ชี้ไปที่องค์ประกอบ (อาจ - สมมุติฐาน) องค์ประกอบx[i+j]ถ้า0≤i + j≤n; มิฉะนั้นพฤติกรรมจะไม่ได้กำหนด


2
ดังนั้นกรณีเดียวx[i]คือเหมือนกันx[i + j]คือเมื่อทั้งสองiและjมีค่า 0?
Rami Yen

8
@RamiYen x[i]เป็นองค์ประกอบเช่นเดียวกับถ้าx[i+j] j==0
ระหว่าง

1
อืมฉันเกลียด "ทไวไลท์โซน" ของซีแมนทิกส์ C ++ ... +1
einpoklum

4
@ einpoklum-reinstateMonica: ไม่มีโซนพลบค่ำจริงๆ มันเป็นเพียง C ++ ที่สอดคล้องกันแม้ในกรณีที่ N = 0 สำหรับอาร์เรย์ขององค์ประกอบ N มีค่าตัวชี้ที่ถูกต้อง N + 1 เพราะคุณสามารถชี้ไปด้านหลังอาร์เรย์ นั่นหมายความว่าคุณสามารถเริ่มต้นที่จุดเริ่มต้นของอาร์เรย์และเพิ่มตัวชี้ N ครั้งเพื่อไปยังจุดสิ้นสุด
MSalters

1
@MaximEgorushkin คำตอบของฉันคือภาษาที่อนุญาตในขณะนี้ การสนทนาเกี่ยวกับคุณต้องการให้มันแทนเป็นหัวข้อ
ระหว่าง

2

ฉันเดาว่าคุณมีคำตอบแล้ว หากคุณมองลึกลงไปอีก: คุณเคยกล่าวไว้ว่าการเพิ่มตัววนซ้ำแบบปิดท้ายเป็น UB ดังนั้น: คำตอบนี้คือตัววนซ้ำ

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

int arr [] = {0,1,2,3,4,5,6,7,8,9}}

int * p = arr; // p ชี้ไปที่องค์ประกอบแรกใน arr

++ p; // p ชี้ไปที่ arr [1]

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

int * e = & arr [10]; // ตัวชี้ผ่านองค์ประกอบสุดท้ายใน arr

ที่นี่เราใช้ตัวดำเนินการตัวห้อยเพื่อจัดทำดัชนีองค์ประกอบที่ไม่มีตัวตน arr มีองค์ประกอบสิบรายการดังนั้นองค์ประกอบสุดท้ายใน arr อยู่ที่ตำแหน่งดัชนี 9 สิ่งเดียวที่เราสามารถทำได้กับองค์ประกอบนี้คือรับที่อยู่ซึ่งเราทำเพื่อเริ่มต้น e เช่นเดียวกับตัววนซ้ำแบบปิดท้าย (§ 3.4.1, p. 106) ตัวชี้ off-the-end ไม่ได้ชี้ไปที่องค์ประกอบ ด้วยเหตุนี้เราจึงไม่อาจปฏิเสธหรือเพิ่มตัวชี้ off-the-end

นี่คือจาก C ++ primer 5 edition โดย Lipmann

ดังนั้นมันเป็น UB ไม่ทำ


-4

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

การเสนอราคามาตรฐานที่ได้รับจาก interjay นั้นเป็นสิ่งที่ดีซึ่งบ่งบอกถึง UB แต่มันเป็นเพียงการตีที่ดีที่สุดเป็นอันดับที่สองในความเห็นของฉันเนื่องจากมันเกี่ยวข้องกับการคำนวณทางคณิตศาสตร์ มีย่อหน้าที่เกี่ยวข้องกับการดำเนินการในคำถามโดยตรง:

[expr.post.incr] / [expr.pre.incr]
ตัวถูกดำเนินการจะเป็น [... ] หรือตัวชี้ไปยังประเภทวัตถุที่กำหนดไว้อย่างสมบูรณ์

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

[basic.compound] 3ทำให้คำสั่งเกี่ยวกับชนิดของตัวชี้หนึ่งอาจจะมีแล้วเป็นไม่มีอีกสามผลการดำเนินงานของคุณอย่างชัดเจนจะตกอยู่ภายใต้ 3.4: ชี้ไม่ถูกต้อง
อย่างไรก็ตามไม่ได้บอกว่าคุณไม่ได้รับอนุญาตให้มีตัวชี้ที่ไม่ถูกต้อง ในทางตรงกันข้ามมันแสดงรายการเงื่อนไขทั่วไปที่พบบ่อยมาก (เช่นระยะเวลาสิ้นสุดการจัดเก็บ) ซึ่งพอยน์เตอร์กลายเป็นไม่ถูกต้องเป็นประจำ เห็นได้ชัดว่าเป็นสิ่งที่อนุญาตให้เกิดขึ้น และแน่นอน:

[basic.stc] 4 การเปลี่ยนทิศทาง
ผ่านค่าตัวชี้ที่ไม่ถูกต้องและส่งผ่านค่าตัวชี้ที่ไม่ถูกต้องไปยังฟังก์ชันการจัดสรรคืนมีพฤติกรรมที่ไม่ได้กำหนด การใช้ค่าตัวชี้อื่น ๆ ที่ไม่ถูกต้องอื่น ๆ นั้นมีพฤติกรรมที่กำหนดไว้ในการนำไปใช้งาน

เรากำลังทำ "อื่น ๆ" มีดังนั้นมันไม่ใช่พฤติกรรมที่ไม่ได้กำหนด แต่การดำเนินงานที่กำหนดไว้ดังนั้นโดยทั่วไปที่อนุญาต (ยกเว้นกรณีที่การดำเนินการอย่างชัดเจนกล่าวว่าสิ่งที่แตกต่างกัน)

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

[basic.compound]
ค่าที่ถูกต้องของชนิดตัวชี้วัตถุหมายถึงที่อยู่ของไบต์ในหน่วยความจำหรือตัวชี้โมฆะ หากมีวัตถุประเภท T ตั้งอยู่ในที่อยู่ [ ... ] บอกว่าจะชี้ไปที่วัตถุที่ไม่คำนึงถึงวิธีการที่คุ้มค่าที่ได้รับ
[หมายเหตุ: ตัวอย่างเช่นที่อยู่หนึ่งที่ผ่านมาจุดสิ้นสุดของอาร์เรย์จะถูกพิจารณาให้ชี้ไปที่วัตถุที่ไม่เกี่ยวข้องกับประเภทองค์ประกอบของอาร์เรย์ที่อาจอยู่ที่ที่อยู่นั้น [ ... ]]

อ่านเป็น: ตกลงใครสนใจ! ตราบใดที่ตัวชี้ชี้ไปที่ใดที่หนึ่งในความทรงจำฉันก็ดี

[basic.stc.dynamic.safety] ค่าตัวชี้เป็นตัวชี้ที่ได้มาอย่างปลอดภัย [blah blah]

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

การใช้งานอาจมีความปลอดภัยของตัวชี้แบบผ่อนคลายซึ่งในกรณีนี้ความถูกต้องของค่าตัวชี้ไม่ขึ้นอยู่กับว่าเป็นค่าตัวชี้ที่ได้มาอย่างปลอดภัยหรือไม่

โอ้ดังนั้นมันอาจไม่สำคัญแค่สิ่งที่ฉันคิด แต่เดี๋ยวก่อน ... "อาจไม่"? นั่นหมายความว่ามันอาจจะเป็นอย่างดี ฉันจะรู้ได้อย่างไร

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

รอดังนั้นเป็นไปได้ที่ฉันต้องโทรหาdeclare_reachable()ตัวชี้ทุกตัวหรือไม่ ฉันจะรู้ได้อย่างไร

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

[expr.reinterpret.cast] 5
ค่าประเภทหนึ่งหรือประเภทการแจงนับสามารถแปลงเป็นตัวชี้อย่างชัดเจน ตัวชี้แปลงเป็นจำนวนเต็มขนาดเพียงพอ [... ] และกลับไปเป็นค่าดั้งเดิมชนิดตัวชี้ [... ] เดียวกัน; การแมประหว่างพอยน์เตอร์กับจำนวนเต็มไม่ได้กำหนดไว้

จับ

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

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

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


1
ตรรกะของคุณมีข้อบกพร่อง "คุณไม่ต้องการวัตถุเลยเหรอ?" ตีความมาตรฐานโดยมุ่งเน้นไปที่กฎข้อเดียว กฎนั้นเกี่ยวกับเวลาในการรวบรวมไม่ว่าโปรแกรมของคุณจะมีรูปแบบที่ดีหรือไม่ มีกฎอีกข้อเกี่ยวกับเวลาทำงาน คุณสามารถพูดคุยเกี่ยวกับการมีอยู่ของวัตถุ ณ เวลาที่กำหนดเท่านั้น โปรแกรมของคุณต้องเป็นไปตามกฎทั้งหมด กฎการคอมไพล์เวลา ณ เวลาการคอมไพล์และกฎการทำในขณะรันไทม์
MSalters

5
คุณมีข้อบกพร่องทางตรรกะที่คล้ายกันกับ "ตกลงใครสนใจ! ตราบใดที่ตัวชี้ชี้ไปที่ใดที่หนึ่งในความทรงจำฉันก็ดี" ไม่คุณต้องปฏิบัติตามกฎทั้งหมด ภาษาที่ยากลำบากเกี่ยวกับ "จุดสิ้นสุดของหนึ่งแถวกำลังเริ่มต้นของอีกแถวหนึ่ง" เพียงแค่ให้สิทธิ์การใช้งานเพื่อจัดสรรหน่วยความจำอย่างต่อเนื่อง ไม่จำเป็นต้องรักษาพื้นที่ว่างระหว่างการจัดสรร นั่นหมายความว่ารหัสของคุณอาจมีค่า A ทั้งคู่เป็นจุดสิ้นสุดของวัตถุอาร์เรย์หนึ่งและการเริ่มต้นของวัตถุอื่น
MSalters

1
"กับดัก" ไม่ใช่สิ่งที่สามารถอธิบายได้ด้วยพฤติกรรม "การใช้งานที่กำหนดไว้" โปรดทราบว่า interjay พบข้อ จำกัด ของ+โอเปอเรเตอร์ (ซึ่ง++ไหล) ซึ่งหมายความว่าการชี้ไปที่ "one-after-the-end" นั้นไม่ได้กำหนดไว้
Martin Bonner สนับสนุนโมนิก้า

1
@PeterCordes: กรุณาอย่าอ่านbasic.stc วรรค 4 มันบอกว่า"ร้าย [ ... ] พฤติกรรมที่ไม่ได้กำหนด. ห้ามการใช้งานอื่น ๆของค่าชี้ไม่ถูกต้องมีการดำเนินการที่กำหนดพฤติกรรม" ฉันไม่ได้ทำให้ผู้คนสับสนโดยใช้คำนั้นสำหรับความหมายอื่น มันเป็นถ้อยคำที่แน่นอน มันไม่ได้เป็นพฤติกรรมที่ไม่ได้กำหนด
Damon

2
เป็นไปได้ยากที่คุณจะพบช่องโหว่สำหรับการเพิ่มภายหลัง แต่คุณไม่ได้อ้างถึงส่วนที่สมบูรณ์เกี่ยวกับการเพิ่มการโพสต์ ฉันจะไม่มองว่าตัวเองในตอนนี้ ตกลงว่าหากมีอย่างใดอย่างหนึ่งก็ไม่ได้ตั้งใจ อย่างไรก็ตามก็ดีถ้า ISO C ++ กำหนดสิ่งเพิ่มเติมสำหรับหน่วยความจำแบบแบน @MaximEgorushkin มีเหตุผลอื่น (เช่นพอยน์เตอร์ล้อมรอบ) เพื่อไม่ให้มีสิ่งใดโดยพลการ ดูความคิดเห็นเกี่ยวกับการเปรียบเทียบตัวชี้ควรลงชื่อหรือไม่ได้ลงทะเบียนใน 64-bit x86?
Peter Cordes
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.