ทำไมอาร์เรย์ C ถึงมีความยาวไม่ถึง 0


13

มาตรฐาน C11 บอกว่าอาร์เรย์ทั้งขนาดและความยาวของตัวแปร "จะมีค่ามากกว่าศูนย์" อะไรคือเหตุผลที่ไม่อนุญาตให้มีความยาว 0

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

GCC ที่น่าสนใจ (และเสียงดังกราว) ให้ส่วนขยายที่อนุญาตให้มีอาร์เรย์ความยาวเป็นศูนย์ Java ยังอนุญาตให้มีอาร์เรย์ของความยาวเป็นศูนย์


7
stackoverflow.com/q/8625572 ... "อาร์เรย์ที่มีความยาวเป็นศูนย์จะยุ่งยากและสับสนในการปรับให้สอดคล้องกับข้อกำหนดที่แต่ละวัตถุมีที่อยู่ที่ไม่ซ้ำกัน"
Robert Harvey

3
@RobertHarvey: ให้struct { int p[1],q[1]; } foo; int *pp = p+1;ไว้ppจะเป็นตัวชี้ที่ถูกต้อง แต่*ppจะไม่มีที่อยู่เฉพาะ เหตุใดตรรกะเดียวกันจึงไม่สามารถถือกับอาร์เรย์ที่มีความยาวเป็นศูนย์ได้ บอกว่าได้รับint q[0]; ภายในโครงสร้าง , qจะอ้างไปยังที่อยู่ที่มีความถูกต้องจะเป็นเหมือนp+1ตัวอย่างข้างต้น
supercat

@DocBrown จากมาตรฐาน C11 6.7.6.2.5 พูดคุยเกี่ยวกับการแสดงออกที่ใช้ในการกำหนดขนาดของ VLA "... ทุกครั้งที่มีการประเมินมันจะมีค่ามากกว่าศูนย์" ฉันไม่รู้เกี่ยวกับ C99 (และดูเหมือนแปลกที่พวกเขาจะเปลี่ยนมัน) แต่ดูเหมือนว่าคุณไม่มีความยาวเป็นศูนย์
Kevin Cox

@KevinCox: มีเวอร์ชั่นออนไลน์ฟรีของมาตรฐาน C11 (หรือบางส่วนที่มีปัญหา) หรือไม่
Doc Brown

เวอร์ชันสุดท้ายไม่สามารถใช้ได้ฟรี (เป็นเรื่องน่าละอาย) แต่คุณสามารถดาวน์โหลดฉบับร่างได้ สุดท้ายร่างเป็นopen-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf
Kevin Cox

คำตอบ:


11

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


19
อาร์เรย์จะไม่ชี้

8
@delnan: ดีถ้าคุณต้องการที่จะพูดคุยเกี่ยวกับมันอาร์เรย์และตัวชี้ทางคณิตศาสตร์ถูกกำหนดไว้เพื่อให้ตัวชี้สามารถใช้สะดวกในการเข้าถึงอาร์เรย์หรือเพื่อจำลองอาร์เรย์ กล่าวอีกนัยหนึ่งก็คือตัวชี้เลขคณิตและการทำดัชนีอาร์เรย์ที่เทียบเท่าใน C แต่ผลลัพธ์จะเหมือนกันอย่างไรก็ตามถ้าความยาวของอาร์เรย์เป็นศูนย์คุณจะยังคงชี้ไปที่อะไร
Robert Harvey

3
@RobertHarvey จริงทั้งหมด แต่คำปิดของคุณ (และคำตอบทั้งหมดในการหวนกลับ) ดูเหมือนเป็นวิธีที่สับสนและสับสนที่จะอธิบายว่าอาร์เรย์ดังกล่าว (ฉันคิดว่านั่นคือสิ่งที่คำตอบนี้เรียกว่า "หน่วยความจำอันจัดสรร")? sizeof0 และวิธีการที่จะทำให้เกิดปัญหา ทั้งหมดที่สามารถอธิบายได้โดยใช้แนวคิดและคำศัพท์ที่เหมาะสมโดยไม่สูญเสียความกระชับหรือความชัดเจน การผสมอาร์เรย์และพอยน์เตอร์จะทำให้เกิดความเสี่ยงในการกระจายอาเรย์ = ตัวชี้ความเข้าใจผิด (ซึ่งมีความสำคัญมากกว่าในบริบทอื่น ๆ ) โดยไม่มีประโยชน์

2
" คุณไม่สามารถใช้ null ได้เนื่องจากอาร์เรย์ 0 ความยาวของคุณจะดูเหมือนตัวชี้ null " - จริงๆแล้วนั่นคือสิ่งที่ Delphi ทำ dynarrays ที่ว่างเปล่าและ longstrings ว่างเปล่าเป็นตัวชี้โมฆะทางเทคนิค
JensG

3
-1, ฉันเต็มไปด้วย @delnan ที่นี่ สิ่งนี้ไม่ได้อธิบายอะไรโดยเฉพาะอย่างยิ่งในบริบทของสิ่งที่ OP เขียนเกี่ยวกับคอมไพเลอร์สำคัญบางตัวที่สนับสนุนแนวคิดของอาร์เรย์ความยาวเป็นศูนย์ ฉันค่อนข้างมั่นใจว่าจะไม่มีการจัดเรียงอาร์เรย์ความยาวเป็นศูนย์ใน C ในวิธีการปรับใช้แบบแยกส่วนไม่ใช่ "นำไปสู่ความโกลาหล"
Doc Brown

6

ลองดูว่าโดยทั่วไปแล้วอาร์เรย์จะถูกวางในหน่วยความจำอย่างไร:

         +----+
arr[0] : |    |
         +----+
arr[1] : |    |
         +----+
arr[2] : |    |
         +----+
          ...
         +----+
arr[n] : |    |
         +----+

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

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

แก้ไข

Java arrays เป็นสัตว์ที่แตกต่างอย่างสิ้นเชิงจาก C และ C ++ พวกเขาไม่ได้เป็นชนิดดั้งเดิม Objectแต่ชนิดที่อ้างอิงมาจาก

แก้ไข2

จุดที่ปรากฏในความคิดเห็นด้านล่าง - ข้อ จำกัด "มากกว่า 0" จะใช้เฉพาะกับอาร์เรย์ที่มีการระบุขนาดผ่านนิพจน์คงที่เท่านั้น VLA ได้รับอนุญาตให้มีความยาว 0 การประกาศ VLA ที่มีนิพจน์ไม่คงที่ที่มีค่าเป็น 0 นั้นไม่ใช่การละเมิดข้อ จำกัด แต่มันก่อให้เกิดพฤติกรรมที่ไม่ได้กำหนด

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

นอกจากนี้ยังไม่มีค่าอะไรที่ C11 นั้นการใช้งานไม่จำเป็นต้องสนับสนุน VLA


3
ขออภัย IMHO คุณพลาดจุดเช่นเดียวกับ Telastyn อาร์เรย์ที่มีความยาวเป็นศูนย์สามารถสร้างความรู้สึกได้อย่างมากมายและการใช้งานที่มีอยู่เช่นเดียวกับที่ OP บอกกับเราเกี่ยวกับการแสดงว่าสามารถทำได้
Doc Brown

@DocBrown: ขั้นแรกฉันกำลังพูดถึงว่าทำไมมาตรฐานภาษาส่วนใหญ่ไม่อนุญาตให้พวกเขา ประการที่สองฉันต้องการตัวอย่างของอาร์เรย์ 0 ความยาวที่เหมาะสมเพราะฉันไม่สามารถคิดได้ การดำเนินการได้มากที่สุดคือการรักษาT a[0]เป็นT *aแต่แล้วทำไมไม่ใช้เพียงT *a?
John Bode

ขออภัย แต่ฉันไม่ซื้อ "เหตุผลเชิงทฤษฎี" เพราะเหตุใดมาตรฐานจึงห้ามสิ่งนี้ อ่านคำตอบของฉันวิธีคำนวณที่อยู่ได้อย่างง่ายดาย และฉันขอแนะนำให้คุณติดตามลิงก์ใน Robert Harveys ความคิดเห็นแรกภายใต้คำถามและอ่านคำตอบที่สองมีตัวอย่างที่มีประโยชน์
Doc Brown

@DocBrown: อ่า structสับ ฉันไม่เคยใช้มันเป็นการส่วนตัว ไม่เคยทำงานกับปัญหาที่จำเป็นต้องมีstructประเภทขนาดแตกต่างกัน
John Bode

2
และอย่าลืม AFAIK ตั้งแต่ C99, C อนุญาตให้อาร์เรย์มีความยาวผันแปรได้ และเมื่อขนาดของอาเรย์เป็นพารามิเตอร์การไม่ต้องปฏิบัติกับค่า 0 เป็นกรณีพิเศษสามารถทำให้โปรแกรมจำนวนมากง่ายขึ้น
Doc Brown

2

โดยปกติคุณจะต้องการให้อาร์เรย์มีขนาดเป็นศูนย์ (ตามความเป็นจริง) เพื่อทราบขนาดของมันในขณะใช้งาน จากนั้นแพ็คในstructและใช้สมาชิกอาร์เรย์ที่มีความยืดหยุ่นเช่นเช่น:

struct my_st {
   unsigned len;
   double flexarray[]; // of size len
};

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

แน่นอนคุณจะจัดสรร:

 unsigned len = some_length_computation();
 struct my_st*p = malloc(sizeof(struct my_st)+len*sizeof(double));
 if (!p) { perror("malloc my_st"); exit(EXIT_FAILURE); };
 p->len = len;
 for (unsigned ix=0; ix<len; ix++)
    p->flexarray[ix] = log(3.0+(double)ix);

AFAIK นี่เป็นไปได้แล้วใน C99 และมันมีประโยชน์มาก

BTW สมาชิกอาร์เรย์ที่ยืดหยุ่นไม่มีอยู่ใน C ++ (เพราะเป็นการยากที่จะกำหนดว่าควรจะสร้างและทำลายเมื่อใดและอย่างไร ดูอย่างไรก็ตามstd :: dynarrayในอนาคต


คุณรู้ไหมว่าพวกมันอาจถูก จำกัด ประเภทที่ไม่สำคัญและจะไม่มีปัญหาใด ๆ
Deduplicator

2

หากการแสดงออกtype name[count]ถูกเขียนในฟังก์ชั่นบางอย่างแล้วคุณบอกให้คอมไพเลอร์ C เพื่อจัดสรรบนสแต็กเฟรมsizeof(type)*countไบต์และคำนวณที่อยู่ขององค์ประกอบแรกในอาร์เรย์

หากการแสดงออกtype name[count]ถูกเขียนนอกฟังก์ชั่นและคำจำกัดความของโครงสร้างทั้งหมดแล้วคุณบอกให้คอมไพเลอร์ C เพื่อจัดสรรในsizeof(type)*countไบต์ส่วนข้อมูลและคำนวณที่อยู่ขององค์ประกอบแรกในอาร์เรย์

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

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

นี่คือเหตุผลที่องค์ประกอบที่ไม่มี count+1ไม่มีอยู่ในcountอาร์เรย์ความยาวดังนั้นนี่คือเหตุผลที่คอมไพเลอร์ C ห้ามมิให้กำหนดอาร์เรย์ที่มีความยาวเป็นศูนย์เป็นตัวแปรทั้งในและนอกฟังก์ชันเนื่องจากเนื้อหาnameนั้นเป็นอย่างไร ที่อยู่ใดที่nameเก็บอย่างแน่นอน

ถ้าpเป็นตัวชี้แสดงว่านิพจน์p[n]นั้นเทียบเท่า*(p + n)

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

เป็นไปได้หรือไม่ที่จะเพิ่มที่อยู่และหมายเลข?

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


คอมไพเลอร์จำนวนมากที่ใช้เพื่ออนุญาตการประกาศอาร์เรย์ที่มีขนาดเป็นศูนย์ก่อนที่มาตรฐานจะห้ามไม่ให้มีการใช้งาน การประกาศดังกล่าวจะไม่เกิดปัญหาหากมีการรับรู้ว่าวัตถุที่NมีขนาดมีN+1ที่อยู่ที่เกี่ยวข้องอันแรกNจะระบุไบต์ที่ไม่ซ้ำกันและสุดท้ายNที่แต่ละจุดเพิ่งผ่านหนึ่งไบต์เหล่านั้น คำจำกัดความดังกล่าวจะใช้งานได้ดีแม้ในกรณีที่ความเสื่อมโทรมอยู่ที่N0.
supercat

1

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


2
โดยทั่วไปคุณจะไม่ทำสิ่งนี้โดยตรง แต่เป็นผลมาจากแมโครหรือเมื่อประกาศอาร์เรย์ความยาวผันแปรด้วยข้อมูลแบบไดนามิก
Kevin Cox

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

1
ชื่ออาร์เรย์คือตัวชี้ค่าคงที่ไปยังหน่วยความจำที่มีอยู่ในอาร์เรย์
ncmathsadist

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

1

จากยุคของ C89 ดั้งเดิมเมื่อ C Standard ระบุว่ามีพฤติกรรมที่ไม่ได้กำหนดสิ่งที่หมายถึงคือ "ทำทุกสิ่งที่จะทำให้การใช้งานบนแพลตฟอร์มเป้าหมายเฉพาะที่เหมาะสมที่สุดสำหรับวัตถุประสงค์ที่ตั้งใจไว้" ผู้เขียนมาตรฐานไม่ต้องการลองเดาว่าพฤติกรรมใดที่เหมาะสมที่สุดสำหรับวัตถุประสงค์เฉพาะ การใช้งาน C89 ที่มีอยู่พร้อมกับส่วนขยาย VLA อาจแตกต่างกัน แต่มีเหตุผลพฤติกรรมเมื่อกำหนดขนาดเป็นศูนย์ (เช่นบางคนอาจถือว่าอาร์เรย์เป็นนิพจน์ที่อยู่ซึ่งให้ NULL ในขณะที่คนอื่น ๆ ถือว่าเป็นนิพจน์ที่อยู่ซึ่งอาจเท่ากับ ตัวแปรอีกตัวหนึ่ง แต่สามารถเพิ่มค่าเป็นศูนย์ได้อย่างปลอดภัยโดยไม่ต้องวางกับดัก) หากรหัสใด ๆ อาจขึ้นอยู่กับพฤติกรรมที่แตกต่างดังกล่าวผู้เขียนมาตรฐานจะไม่ '

แทนที่จะพยายามเดาว่าการติดตั้งใช้งานแบบใดที่อาจทำหรือแนะนำว่าพฤติกรรมใด ๆ ควรได้รับการพิจารณาให้เหนือชั้นกว่าผู้อื่นผู้เขียนมาตรฐานอนุญาตให้ผู้ใช้งาน ใช้งานใช้ดุลยพินิจในการจัดการกรณีดังกล่าวเท่าที่เห็นสมควร การนำไปปฏิบัติที่ใช้ malloc () ด้านหลังของฉากอาจถือว่าที่อยู่ของอาร์เรย์เป็น NULL (ถ้าขนาดศูนย์เป็นศูนย์ malloc ให้ผลเป็นโมฆะ) ผู้ที่ใช้การคำนวณแบบสแต็ก - ที่อยู่อาจให้ตัวชี้ที่ตรงกับที่อยู่ของตัวแปรอื่น ๆ สิ่งอื่น ๆ. ฉันไม่คิดว่าพวกเขาคาดหวังว่านักเขียนคอมไพเลอร์จะออกนอกเส้นทางเพื่อทำให้คดีมุมขนาดศูนย์เป็นไปอย่างไร้ประโยชน์

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