size_t เทียบกับ uintptr_t


246

มาตรฐาน C รับประกันว่าsize_tเป็นประเภทที่สามารถเก็บดัชนีอาร์เรย์ใด ๆ ซึ่งหมายความว่ามีเหตุผลsize_tควรจะสามารถเก็บตัวชี้ใด ๆ ฉันได้อ่านในเว็บไซต์บางแห่งที่ฉันพบใน Googles ว่าถูกกฎหมายและ / หรือควรทำงาน:

void *v = malloc(10);
size_t s = (size_t) v;

ดังนั้นใน C99 มาตรฐานแนะนำintptr_tและuintptr_tประเภทซึ่งลงนามและไม่ได้ลงนามประเภทรับประกันว่าจะสามารถเก็บพอยน์เตอร์:

uintptr_t p = (size_t) v;

ดังนั้นความแตกต่างระหว่างการใช้size_tและuintptr_tคืออะไร? ทั้งสองแบบไม่ได้ลงนามและทั้งคู่ควรจะสามารถถือตัวชี้ประเภทใดก็ได้ มีเหตุผลที่น่าสนใจจริง ๆuintptr_tหรือไม่ที่จะใช้(หรือดีกว่า, a void *) มากกว่า a size_t, มากกว่าความชัดเจน? ในโครงสร้างทึบซึ่งเขตข้อมูลจะถูกจัดการโดยฟังก์ชันภายในเท่านั้นมีเหตุผลใดที่จะไม่ทำเช่นนี้หรือไม่?

ด้วยโทเค็นเดียวกันptrdiff_tมีชนิดที่มีลายเซ็นสามารถถือความแตกต่างของตัวชี้ได้และดังนั้นจึงสามารถจับตัวชี้ส่วนใหญ่ได้ดังนั้นจึงแตกต่างจากintptr_tอย่างไร

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

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

คำตอบ:


236

size_tเป็นประเภทที่สามารถเก็บดัชนีอาร์เรย์ใด ๆ ซึ่งหมายความว่า size_t ตามหลักเหตุผลควรจะสามารถเก็บตัวชี้ประเภทใดก็ได้

ไม่จำเป็น! กลับไปสู่ยุคสมัยของการแบ่งกลุ่มสถาปัตยกรรม 16 บิตตัวอย่างเช่นอาร์เรย์อาจถูก จำกัด ให้อยู่ในส่วนเดียว (ดังนั้นsize_tจะทำแบบ 16 บิต) แต่คุณสามารถมีหลายส่วนได้ (ดังนั้นประเภท 32 บิตintptr_tจะต้องเลือก ส่วนเช่นเดียวกับการชดเชยภายในมัน) ฉันรู้ว่าสิ่งเหล่านี้ฟังดูแปลกในวันนี้ของสถาปัตยกรรม unsegmented ที่อยู่เหมือนกัน แต่มาตรฐานต้องรองรับความหลากหลายที่กว้างกว่า "มีอะไรปกติในปี 2009" คุณรู้! -)


6
นี้พร้อมกับคนอื่น ๆ หลายคนที่กระโดดลงมาถึงข้อสรุปเดียวกันอธิบายความแตกต่างระหว่างsize_tและuintptr_tแต่สิ่งที่เกี่ยวptrdiff_tและintptr_t- จะไม่ทั้งสองเหล่านี้สามารถที่จะเก็บช่วงเดียวกันของค่าในเกือบทุกแพลตฟอร์มใด? เหตุใดจึงมีทั้งประเภทจำนวนเต็มขนาดตัวชี้ที่ลงนามและไม่ได้ลงนามโดยเฉพาะอย่างยิ่งถ้าptrdiff_tทำหน้าที่ตามวัตถุประสงค์ของประเภทจำนวนเต็มขนาดตัวชี้ลงนาม
คริสลัทซ์

8
วลีสำคัญมี " เกือบทุกแพลตฟอร์ม" @Chris การใช้งานมีอิสระที่จะ จำกัด พอยน์เตอร์ให้อยู่ในช่วง 0xf000-0xffff - สิ่งนี้ต้องการ 16 บิต intptr_t แต่เพียง 12/13 บิต ptrdiff_t
paxdiablo

29
@Chris เฉพาะสำหรับพอยน์เตอร์ที่อยู่ภายในอาร์เรย์เดียวกันเท่านั้น ดังนั้นในสถาปัตยกรรม 16 บิตแบบแบ่งส่วนเดียวกัน (อาร์เรย์ต้องอยู่ภายในเซ็กเมนต์เดียว แต่สองอาร์เรย์ที่แตกต่างกันสามารถอยู่ในเซ็กเมนต์ที่ต่างกัน) พอยน์เตอร์ต้องมี 4 ไบต์ แต่ความแตกต่างของตัวชี้อาจเป็น 2 ไบต์!
Alex Martelli

6
@AlexMartelli: ยกเว้นความแตกต่างของตัวชี้อาจเป็นบวกหรือลบ มาตรฐานต้องsize_tมีอย่างน้อย 16 บิต แต่ptrdiff_tอย่างน้อย 17 บิต (ซึ่งในทางปฏิบัติหมายความว่ามันอาจจะเป็นอย่างน้อย 32 บิต)
Keith Thompson

3
ไม่ต้องคำนึงถึงสถาปัตยกรรมที่แบ่งเป็นส่วน ๆ แล้วสถาปัตยกรรมสมัยใหม่เช่น x86-64 คืออะไร การใช้งานในช่วงต้นของสถาปัตยกรรมนี้ให้พื้นที่ที่สามารถกำหนดแอดเดรสได้ 48 บิตเท่านั้น แต่พอยน์เตอร์นั้นเป็นชนิดข้อมูล 64 บิต บล็อกหน่วยความจำที่ต่อเนื่องที่ใหญ่ที่สุดที่คุณสามารถระบุได้อย่างเหมาะสมคือ 48- บิตดังนั้นฉันต้องจินตนาการว่าSIZE_MAXไม่ควรเป็น 2 ** 64 นี่คือการใช้ที่อยู่แบนใจคุณ; ไม่จำเป็นต้องแบ่งส่วนเพื่อให้มีความไม่ตรงกันระหว่างSIZE_MAXและช่วงของตัวชี้ข้อมูล
Andon M. Coleman

89

เกี่ยวกับคำสั่งของคุณ:

"มาตรฐาน C รับรองว่าsize_tเป็นประเภทที่สามารถเก็บดัชนีอาเรย์ใด ๆ ได้ซึ่งหมายความว่าตามหลักเหตุผลsize_tควรจะสามารถเก็บตัวชี้ประเภทใดก็ได้"

นี้เป็นจริงเข้าใจผิด (ความเข้าใจผิดที่เกิดจากเหตุผลที่ไม่ถูกต้อง) (ก) คุณอาจคิดว่าสิ่งที่ตามมาจากอดีต แต่ไม่ใช่ในกรณีนี้

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

C99 ระบุว่าขีด จำกัด สูงสุดของsize_tตัวแปรถูกกำหนดโดยSIZE_MAXและสามารถมีค่าต่ำสุดเท่ากับ 65535 (ดู C99 TR3, 7.18.3, ไม่เปลี่ยนแปลงใน C11) พอยน์เตอร์จะค่อนข้าง จำกัด หากถูก จำกัด ในช่วงนี้ในระบบที่ทันสมัย

ในทางปฏิบัติคุณอาจพบว่าข้อสันนิษฐานของคุณมี แต่นั่นไม่ใช่เพราะมาตรฐานรับรอง เพราะจริงๆแล้วมันไม่รับประกัน


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

ลูกสุนัขทุกตัวน่ารัก สิ่งนี้น่ารัก ดังนั้นสิ่งนี้จะต้องเป็นลูกสุนัข

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

ข้อความนี้คล้ายกับข้อความสั่งแรกของคุณซึ่งไม่จำเป็นต้องหมายถึงข้อความที่สอง


แทนที่จะพิมพ์สิ่งที่ฉันพูดในความคิดเห็นของ Alex Martelli ฉันจะพูดขอบคุณสำหรับคำอธิบาย แต่ขอย้ำคำถามครึ่งหลังของคำถามของฉัน ( ส่วนptrdiff_tvs. intptr_t)
Chris Lutz

5
@Ivan เช่นเดียวกับการสื่อสารส่วนใหญ่จะต้องมีความเข้าใจร่วมกันของรายการพื้นฐานบางอย่าง หากคุณเห็นคำตอบนี้ว่า "สนุกกับการล้อเล่น" ฉันขอยืนยันว่านี่เป็นความเข้าใจผิดที่ฉันตั้งใจ สมมติว่าคุณอ้างถึงความคิดเห็น 'การเข้าใจผิดอย่างมีเหตุผล' ของฉัน (ฉันไม่เห็นความเป็นไปได้อื่น ๆ ) ซึ่งหมายถึงคำแถลงข้อเท็จจริงไม่ใช่คำสั่งบางอย่างที่ทำโดย OP หากคุณต้องการแนะนำการปรับปรุงที่เป็นรูปธรรมบางประการเพื่อลดความเป็นไปได้ที่จะเกิดความเข้าใจผิด (แทนที่จะเป็นเพียงแค่การร้องเรียนทั่วไป) ฉันยินดีที่จะพิจารณา
paxdiablo

1
@ivan_pozdeev - เป็นคู่ของการแก้ไขที่น่าสะพรึงกลัวและรุนแรงและฉันไม่เห็นหลักฐานว่า paxdiablo นั้น "สนุกกับการล้อเล่น" กับใครก็ตาม ถ้าฉันเป็น OP ฉันจะย้อนกลับไปทันที ....
อดีตนิฮาอิ

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

1
@paxdiablo โอเคฉันเดาว่า "นี่มันผิดพลาดจริง ๆ " มีคนอุดหนุนน้อยกว่า
ivan_pozdeev

36

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

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

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

มิฉะนั้นคุณสามารถใช้unsigned long(หรือในยุคปัจจุบันนี้unsigned long long) เพื่อทุกสิ่ง ขนาดไม่ใช่ทุกอย่างชื่อชนิดมีความหมายซึ่งมีประโยชน์เนื่องจากช่วยอธิบายโปรแกรม


ฉันเห็นด้วย แต่ฉันกำลังพิจารณาบางสิ่งเกี่ยวกับการแฮ็ก / เคล็ดลับ (ซึ่งแน่นอนว่าฉันจะบันทึกเป็นเอกสารอย่างชัดเจน) เกี่ยวกับการจัดเก็บประเภทตัวชี้ในsize_tฟิลด์
คริสลัทซ์

@MarkAdler Standard ไม่ต้องการให้พอยน์เตอร์แสดงเป็นจำนวนเต็มทั้งหมด: ประเภทของพอยน์เตอร์ใด ๆ อาจถูกแปลงเป็นประเภทจำนวนเต็ม ยกเว้นตามที่ระบุไว้ก่อนหน้าผลที่ได้คือการใช้งานที่กำหนดไว้ หากไม่สามารถแสดงผลลัพธ์ในประเภทจำนวนเต็มลักษณะการทำงานจะไม่ได้กำหนด ผลลัพธ์ไม่จำเป็นต้องอยู่ในช่วงของค่าของประเภทจำนวนเต็มใด ๆ ดังนั้นเฉพาะvoid*, intptr_tและuintptr_tมีการรับประกันที่จะสามารถที่จะเป็นตัวแทนของตัวชี้ใด ๆ กับข้อมูล
Andrew Svietlichnyy

12

เป็นไปได้ว่าขนาดของอาร์เรย์ที่ใหญ่ที่สุดนั้นเล็กกว่าตัวชี้ นึกถึงสถาปัตยกรรมที่แบ่งกลุ่ม - พอยน์เตอร์อาจเป็น 32 บิต แต่เซ็กเมนต์เดียวอาจสามารถระบุได้เพียง 64KB (ตัวอย่างเช่นสถาปัตยกรรม 8086 แบบเรียลโหมดเก่า)

แม้ว่าสิ่งเหล่านี้จะไม่ได้ใช้งานทั่วไปในเครื่องเดสก์ท็อปอีกต่อไป แต่มาตรฐาน C นั้นมีจุดประสงค์เพื่อสนับสนุนสถาปัตยกรรมขนาดเล็กและพิเศษ ยังคงมีระบบฝังตัวที่ได้รับการพัฒนาด้วยซีพียู 8 หรือ 16 บิต


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

ตัวชี้การทำดัชนีได้รับการสนับสนุนทางเทคนิคเฉพาะกับขนาดของอาร์เรย์ที่ชี้ไปเท่านั้นดังนั้นหากอาร์เรย์นั้น จำกัด ขนาด 64KB นั่นคือทั้งหมดที่ตัวชี้ทางคณิตศาสตร์นั้นจำเป็นต้องได้รับการสนับสนุน อย่างไรก็ตามคอมไพเลอร์ MS-DOS ได้สนับสนุนโมเดลหน่วยความจำ 'ใหญ่' ที่พอยน์เตอร์พอยน์เตอร์ (พอยน์เตอร์แบบแบ่งส่วน 32 บิต) ถูกจัดการเพื่อให้พวกเขาสามารถจัดการกับหน่วยความจำทั้งหมดเป็นอาเรย์เดี่ยวได้ ค่อนข้างน่าเกลียด - เมื่อค่าออฟเซ็ตที่เพิ่มขึ้นมีค่าเท่ากับ 16 (หรือบางอย่าง) การชดเชยจะถูกตัดเป็น 0 และส่วนเซกเมนต์ก็เพิ่มขึ้น
ไมเคิลเสี้ยน

7
อ่านen.wikipedia.org/wiki/C_memory_model#Memory_segmentation และร้องไห้ให้กับโปรแกรมเมอร์ MS-DOS ที่เสียชีวิตเพื่อเราจะได้เป็นอิสระ
Justicle

แย่กว่านั้นคือฟังก์ชั่น stdlib ไม่ได้ดูแลคำสำคัญมาก 16bit MS-C สำหรับทุกstrฟังก์ชั่นและ Borland แม้สำหรับmemฟังก์ชั่น ( memset, memcpy, memmove) นั่นหมายความว่าคุณสามารถเขียนทับส่วนหนึ่งของหน่วยความจำได้เมื่อออฟเซ็ตโอเวอร์โฟลว์นั่นสนุกที่จะทำการดีบักบนแพลตฟอร์มแบบฝังของเรา
Patrick Schlüter

@Justicle: สถาปัตยกรรม 8086 แบ่งไม่ได้รับการสนับสนุนอย่างดีใน C แต่ฉันรู้ว่าไม่มีสถาปัตยกรรมอื่น ๆ ที่มีประสิทธิภาพมากขึ้นในกรณีที่พื้นที่ที่อยู่ 1MB เพียงพอ แต่ 64K จะไม่เป็นเช่นนั้น JVM สมัยใหม่บางตัวใช้การกำหนดแอดเดรสเป็นโหมด x86 จริงโดยใช้การอ้างอิงวัตถุแบบ 32 บิตที่เหลือ 3 บิตเพื่อสร้างที่อยู่ฐานวัตถุในพื้นที่ที่อยู่ 32GB
supercat

5

ฉันจะจินตนาการ (และนี่จะเป็นชื่อของทุกประเภท) ว่าเป็นการบ่งบอกความตั้งใจของคุณในโค้ดได้ดียิ่งขึ้น

ตัวอย่างเช่นแม้ว่าunsigned shortและwchar_tมีขนาดเท่ากันใน Windows (ฉันคิดว่า) การใช้wchar_tแทนที่จะunsigned shortแสดงความตั้งใจที่คุณจะใช้มันเพื่อเก็บอักขระขนาดใหญ่แทนที่จะเป็นเพียงจำนวนที่กำหนดเอง


แต่มีความแตกต่างที่นี่ - ในระบบของฉันwchar_tมีขนาดใหญ่กว่าการunsigned shortใช้อย่างใดอย่างหนึ่งสำหรับอีกอันหนึ่งจะผิดพลาดและสร้างความกังวลเรื่องการพกพาที่จริงจัง (และทันสมัย) ในขณะที่ความกังวลเรื่องการพกพาระหว่างsize_tและuintptr_tดูเหมือนจะอยู่ในดินแดนไกล 1980 บางสิ่งบางอย่าง (แทงสุ่มในที่มืดในวันที่มี)
คริสลัทซ์

Touch?! แต่แล้วอีกครั้งsize_tและuintptr_tยังคงมีการใช้โดยนัยในชื่อของพวกเขา
dreamlax

พวกเขาทำและฉันต้องการทราบว่ามีแรงจูงใจสำหรับสิ่งนี้มากกว่าความชัดเจน และปรากฎว่ามี
คริสลัทซ์

3

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

ดังนั้นแน่นอนว่าสิ่งต่าง ๆ มีความแตกต่างกันไปเราจึงมีความต้องการไม่มากนัก

แต่แม้ใน LP64 ซึ่งเป็นกระบวนทัศน์ที่ค่อนข้างธรรมดาเราต้องการ size_t และ ssize_t สำหรับส่วนต่อประสานการโทรของระบบ ใครจะนึกภาพมรดกที่ จำกัด มากขึ้นหรือระบบในอนาคตซึ่งการใช้ชนิด 64- บิตแบบเต็มนั้นมีราคาแพงและพวกเขาอาจต้องการที่จะเลือกใช้ I / O ops ที่มีขนาดใหญ่กว่า 4GB แต่ยังคงมีตัวชี้ 64 บิต

ฉันคิดว่าคุณต้องสงสัย: สิ่งที่อาจได้รับการพัฒนาสิ่งที่อาจเกิดขึ้นในอนาคต (อาจเป็นพอยน์เตอร์ทั้งระบบแบบ 128 บิตแบบกระจาย แต่ไม่เกิน 64 บิตในการเรียกใช้ระบบหรืออาจเป็นข้อ จำกัด 32 บิตแบบ "ดั้งเดิม" :-) ภาพที่ระบบดั้งเดิมอาจได้รับตัวรวบรวม C ใหม่ .. .

ดูสิ่งที่มีอยู่ในตอนนั้นด้วย นอกจากรุ่นหน่วยความจำโหมด zillion 286 แล้วส่วนหลักของ CDC 60-bit word / 18-bit แล้วซีรี่ส์ Cray ล่ะ? ไม่เป็นไรปกติ ILP64, LP64, LLP64 (ฉันมักจะคิดว่าไมโครซอฟท์แสร้งทำกับ LLP64 มันควรจะเป็น P64) ฉันสามารถนึกภาพได้ว่าคณะกรรมการที่พยายามครอบคลุมฐานทั้งหมด ...


-9
int main(){
  int a[4]={0,1,5,3};
  int a0 = a[0];
  int a1 = *(a+1);
  int a2 = *(2+a);
  int a3 = 3[a];
  return a2;
}

การทำเช่นนั้น intptr_t จะต้องแทนที่ size_t และวีซ่าในทางกลับกันเสมอ


10
ทั้งหมดนี้แสดงให้เห็นว่าเป็นไวยากรณ์เฉพาะของการจัดทำดัชนีอาร์เรย์ Array ถูกกำหนดในแง่ของ x [y] เทียบเท่ากับ * (x + y) และเนื่องจาก +3 และ 3 + a เหมือนกันในประเภทและค่าคุณจึงสามารถ ใช้ 3 [a] หรือ [3]
Fred Nurk
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.