การส่งอาร์เรย์เป็นอาร์กิวเมนต์ไปยังฟังก์ชันใน C


88

ฉันเขียนฟังก์ชันที่มีอาร์เรย์เป็นอาร์กิวเมนต์และเรียกใช้โดยการส่งผ่านค่าอาร์เรย์ดังนี้

สิ่งที่ฉันพบคือแม้ว่าฉันจะเรียกarraytest()ใช้ฟังก์ชันโดยการส่งผ่านค่า แต่สำเนาต้นฉบับint arr[]ก็เปลี่ยนไป

คุณช่วยอธิบายได้ไหมว่าทำไม?


1
คุณกำลังส่งผ่านอาร์เรย์โดยการอ้างอิง แต่คุณกำลังแก้ไขเนื้อหา - ด้วยเหตุนี้คุณจึงเห็นการเปลี่ยนแปลงในข้อมูล
Shaun Wilde

main()ต้องกลับint.
underscore_d

คำตอบ:


140

เมื่อส่งอาร์เรย์เป็นพารามิเตอร์สิ่งนี้

หมายความว่าเหมือนกับ

ดังนั้นคุณกำลังแก้ไขค่าใน main

ด้วยเหตุผลทางประวัติศาสตร์อาร์เรย์ไม่ใช่พลเมืองชั้นหนึ่งและไม่สามารถส่งผ่านค่าได้


3
สัญกรณ์ใดดีกว่าภายใต้สถานการณ์ใด
Ramon Martinez

22
@Ramon - ฉันจะใช้ตัวเลือกที่สองเพราะดูเหมือนจะสับสนน้อยลงและบ่งบอกได้ดีกว่าว่าคุณไม่ได้รับสำเนาของอาร์เรย์
Bo Persson

2
คุณสามารถอธิบาย "เหตุผลทางประวัติศาสตร์" ได้หรือไม่? ฉันคิดว่าการส่งผ่านค่าจะต้องใช้สำเนาและทำให้หน่วยความจำเสียไป .. ขอบคุณ
Jacquelyn.Marquardt

5
@lucapozzobon - แต่เดิม C ไม่มีค่าใด ๆ เลยยกเว้นค่าเดียว จนกระทั่งstructมีการเพิ่มภาษาที่มีการเปลี่ยนแปลงนี้ จากนั้นก็ถือว่าสายเกินไปที่จะเปลี่ยนกฎสำหรับอาร์เรย์ มีผู้ใช้แล้ว 10 ราย :-)
Bo Persson

1
... หมายความว่าเหมือนกับvoid arraytest(int a[1000])ฯลฯทุกประการคำตอบเพิ่มเติมที่นี่: stackoverflow.com/a/51527502/4561887
Gabriel Staples

9

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

ดังนั้นคุณกำลังแก้ไขค่าดั้งเดิม

ขอบคุณ !!!


ฉันกำลังมองหาตัวอย่างที่สองของคุณช่วยอธิบายให้ละเอียดว่าแต่ละวิธีมีข้อดีอย่างไร
Puck

8

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



7

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

นี่คือคำพูดจากK & R2nd :

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

การเขียน:

มีความหมายเหมือนกับการเขียน:

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

สำหรับข้อมูลเพิ่มเติมฉันขอแนะนำให้อ่านสิ่งนี้จริงๆ

นอกจากนี้คุณสามารถค้นหาคำตอบอื่น ๆ เกี่ยวกับ SO ได้ที่นี่


6

คุณกำลังส่งค่าของตำแหน่งหน่วยความจำของสมาชิกตัวแรกของอาร์เรย์

ดังนั้นเมื่อคุณเริ่มแก้ไขอาร์เรย์ภายในฟังก์ชันคุณกำลังแก้ไขอาร์เรย์เดิม

โปรดจำไว้ว่าเป็นa[1]*(a+1)


1
ฉันคิดว่ามี () หายไปสำหรับ * a + 1 ควรจะเป็น * (a + 1)
ShinTakezou

@Shin ขอบคุณมาสักพักแล้วที่ฉันเล่นกับ C.
alex

6

ใน C ยกเว้นกรณีพิเศษบางประการการอ้างอิงอาร์เรย์จะ "สลายตัว" ไปยังตัวชี้ไปยังองค์ประกอบแรกของอาร์เรย์เสมอ ดังนั้นจึงไม่สามารถส่งอาร์เรย์ "ตามค่า" ได้ อาร์เรย์ในการเรียกใช้ฟังก์ชันจะถูกส่งผ่านไปยังฟังก์ชันเป็นตัวชี้ซึ่งคล้ายกับการส่งผ่านอาร์เรย์โดยการอ้างอิง

แก้ไข: มีกรณีพิเศษสามกรณีที่อาร์เรย์ไม่สลายตัวเป็นตัวชี้ไปที่องค์ประกอบแรก:

  1. sizeof aไม่เหมือนกับsizeof (&a[0]).
  2. &aไม่เหมือนกับ&(&a[0])(และไม่เหมือนกับ&a[0])
  3. char b[] = "foo"ไม่เหมือนกับchar b[] = &("foo").

ถ้าฉันส่งอาร์เรย์ไปยังฟังก์ชัน ยกตัวอย่างเช่นฉันสร้างอาร์เรย์int a[10]และกำหนดค่าสุ่มแต่ละองค์ประกอบ ตอนนี้ถ้าฉันส่งอาร์เรย์นี้ไปยังฟังก์ชันโดยใช้int y[]หรือint y[10]หรือint *yจากนั้นในฟังก์ชันนั้นฉันใช้sizeof(y)คำตอบจะเป็นการจัดสรรตัวชี้ไบต์ ดังนั้นในกรณีนี้มันจะสลายตัวเป็นตัวชี้มันจะเป็นประโยชน์ถ้าคุณรวมสิ่งนี้ด้วย ดูโพสต์นี้img.org/image/prhleuezd
Suraj Jain

ถ้าฉันใช้การsizeofดำเนินการในฟังก์ชันในอาร์เรย์เดิมที่เรากำหนดไว้มันจะสลายตัวเป็นอาร์เรย์ แต่ถ้าฉันส่งผ่านในฟังก์ชันอื่นแล้วมีsizeofตัวดำเนินการใช้มันจะสลายตัวเป็นตัวชี้
Suraj Jain


ฉันรู้ว่านี่มันเก่าแล้ว สองคำถามที่เกิดขึ้นถ้าใครที่เห็นนี้ :) 1. @ThomSmith เขียนว่า&aไม่มากเช่นเดียวกับ&a[0]เมื่อaเป็นอาร์เรย์ ยังไง? ในโปรแกรมทดสอบของฉันทั้งสองแสดงเหมือนกันทั้งในฟังก์ชันที่มีการประกาศอาร์เรย์และเมื่อส่งผ่านไปยังฟังก์ชันอื่น 2. นักเขียนเขียนว่า " char b[] = "foo"ไม่เหมือนกับchar b[] = &("foo")" สำหรับฉันตอนหลังไม่ได้รวบรวม มันเป็นแค่ฉัน?
Aviv Cohn

6

การส่งอาร์เรย์หลายมิติเป็นอาร์กิวเมนต์ไปยังฟังก์ชัน การส่งผ่านอาร์เรย์ dim หนึ่งอาร์เรย์เนื่องจากอาร์กิวเมนต์นั้นไม่สำคัญมากหรือน้อย ลองมาดูกรณีที่น่าสนใจยิ่งขึ้นในการส่งผ่านอาร์เรย์ 2 สลัว ใน C คุณไม่สามารถใช้ตัวชี้เพื่อสร้างตัวชี้ ( int **) แทนอาร์เรย์ 2 สลัวได้ ลองดูตัวอย่าง:

ที่นี่ฉันได้ระบุฟังก์ชันที่ใช้เป็นอาร์กิวเมนต์แรกตัวชี้ไปยังอาร์เรย์ของจำนวนเต็ม 5 ตัว ฉันสามารถส่งเป็นอาร์กิวเมนต์อาร์เรย์ 2 สลัวที่มี 5 คอลัมน์:

คุณอาจคิดที่จะกำหนดฟังก์ชันทั่วไปที่สามารถรับอาร์เรย์ 2 dim และเปลี่ยนลายเซ็นฟังก์ชันได้ดังนี้:

รหัสนี้จะคอมไพล์ แต่คุณจะได้รับข้อผิดพลาดรันไทม์เมื่อพยายามกำหนดค่าในลักษณะเดียวกับในฟังก์ชันแรก ดังนั้นใน C อาร์เรย์หลายมิติจึงไม่เหมือนกับพอยน์เตอร์ถึงพอยน์เตอร์ ... ถึงพอยน์เตอร์ An int(*arr)[5]เป็นตัวชี้ไปยังอาร์เรย์ของ 5 องค์ประกอบและint(*arr)[6] เป็นตัวชี้ไปยังอาร์เรย์ของ 6 องค์ประกอบและเป็นตัวชี้ไปยังประเภทต่างๆ!

วิธีการกำหนดอาร์กิวเมนต์ของฟังก์ชันสำหรับมิติข้อมูลที่สูงขึ้น? ง่ายๆแค่ทำตามแบบ! นี่คือฟังก์ชั่นเดียวกันที่ปรับให้รับอาร์เรย์ 3 มิติ:

สิ่งที่คุณคาดหวังมันสามารถใช้เป็นอาร์กิวเมนต์อาร์เรย์ 3 สลัวใดก็ได้ที่มีในมิติที่สอง 4 องค์ประกอบและในมิติที่สาม 5 องค์ประกอบ อะไรแบบนี้ก็โอเค:

แต่เราต้องระบุขนาดมิติข้อมูลทั้งหมดจนถึงขนาดแรก


6

การใช้อาร์เรย์มาตรฐานใน C พร้อมการสลายตัวแบบธรรมชาติจากอาร์เรย์เป็น ptr

@ Bo Persson ระบุอย่างถูกต้องในคำตอบที่ยอดเยี่ยมของเขาที่นี่ :

เมื่อส่งอาร์เรย์เป็นพารามิเตอร์สิ่งนี้

หมายความว่าเหมือนกับ

อย่างไรก็ตามขอฉันเพิ่มด้วยว่าทั้งสองรูปแบบข้างต้นยัง:

  1. หมายความว่าเหมือนกับ

  2. ซึ่งหมายความว่าเหมือนกับ

  3. ซึ่งหมายความว่าเหมือนกับ

  4. ซึ่งหมายความว่าเหมือนกับ

  5. เป็นต้น

ในทุกตัวอย่างอาร์เรย์ข้างต้นประเภทพารามิเตอร์อินพุตจะสลายตัวเป็น anint *และสามารถเรียกได้โดยไม่มีคำเตือนและไม่มีข้อผิดพลาดแม้จะ-Wall -Wextra -Werrorเปิดตัวเลือกการสร้างไว้แล้วก็ตาม(ดูที่repo ของฉันที่นี่สำหรับรายละเอียดเกี่ยวกับตัวเลือกการสร้างทั้ง 3 นี้) เช่น นี้:

เป็นเรื่องของความเป็นจริง "ขนาด" ค่า (ก[0], [1], [2], [1000]ฯลฯ ) ภายในพารามิเตอร์อาร์เรย์ที่นี่เป็นที่เห็นได้ชัดเพียงเพื่อความงาม / วัตถุประสงค์เอกสารด้วยตนเองและสามารถเป็นจำนวนเต็มบวกใด ๆ (size_tพิมพ์ฉันคิด) ที่คุณต้องการ!

อย่างไรก็ตามในทางปฏิบัติคุณควรใช้เพื่อระบุขนาดขั้นต่ำของอาร์เรย์ที่คุณคาดหวังว่าฟังก์ชันจะได้รับดังนั้นเมื่อเขียนโค้ดคุณจะติดตามและตรวจสอบได้ง่าย มาตรฐานMISRA-C-2012 ( ซื้อ / ดาวน์โหลด PDF เวอร์ชัน 236-pg 2012 ของมาตรฐานราคา 15.00 ปอนด์ที่นี่ ) ไปไกลถึงสถานะ (เน้นเพิ่ม):

กฎข้อ 17.5 อาร์กิวเมนต์ของฟังก์ชันที่สอดคล้องกับพารามิเตอร์ที่ประกาศว่ามีประเภทอาร์เรย์ต้องมีจำนวนองค์ประกอบที่เหมาะสม

...

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

...

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

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


บังคับประเภทความปลอดภัยในอาร์เรย์ใน C

ดังที่ @Winger Sendon ชี้ให้เห็นในความคิดเห็นด้านล่างคำตอบของฉันเราสามารถบังคับให้ C รักษาประเภทอาร์เรย์ให้แตกต่างกันตามขนาดอาร์เรย์ !

ขั้นแรกคุณต้องตระหนักว่าในตัวอย่างของฉันข้างต้นโดยใช้สิ่งint array1[2];นี้: arraytest(array1);ทำให้array1สลายตัวเป็นint *ไฟล์. อย่างไรก็ตามหากคุณใช้ที่อยู่ array1แทนและโทรarraytest(&array1)ไปคุณจะมีพฤติกรรมที่แตกต่างไปจากเดิมอย่างสิ้นเชิง! ตอนนี้ยังไม่สลายตัวเป็นint *! ประเภทของ&array1คือint (*)[2]ซึ่งหมายถึง"ตัวชี้ไปยังอาร์เรย์ขนาด 2 ของ int"หรือ"ตัวชี้ไปยังอาร์เรย์ขนาด 2 ของประเภท int"แทน ดังนั้นคุณสามารถ FORCE C เพื่อตรวจสอบความปลอดภัยของประเภทในอาร์เรย์เช่นนี้:

รูปแบบนี้เป็นเรื่องยากที่จะอ่าน แต่คล้ายกับที่ของตัวชี้ฟังก์ชัน เครื่องมือออนไลน์cdeclบอกเราว่าint (*a)[2]: "ประกาศเป็นตัวชี้ไปยังอาร์เรย์ 2 ของ int" (ตัวชี้ไปที่อาร์เรย์ 2 intวินาที) อย่าสับสนกับเวอร์ชันที่ไม่มีวงเล็บ: int * a[2]ซึ่งหมายความว่า: "ประกาศเป็นอาร์เรย์ 2 ของตัวชี้เป็น int" (อาร์เรย์ 2 พอยน์เตอร์ถึงint)

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

อย่างไรก็ตามสิ่งนี้จะทำให้เกิดคำเตือน:

คุณอาจจะทดสอบรหัสที่นี่

ในการบังคับให้คอมไพเลอร์ C เปลี่ยนคำเตือนนี้เป็นข้อผิดพลาดดังนั้นคุณต้องเรียกarraytest(&array1);ใช้เฉพาะอาร์เรย์อินพุตที่มีขนาดและประเภทที่ถูกต้องเท่านั้นให้int array1[2];เพิ่ม-Werrorตัวเลือกการสร้างของคุณ หากเรียกใช้โค้ดทดสอบด้านบนบน onlinegdb.com ให้คลิกไอคอนรูปเฟืองที่ด้านบนขวาและคลิกที่ "Extra Compiler Flags" เพื่อพิมพ์ตัวเลือกนี้ในตอนนี้คำเตือนนี้:

จะกลายเป็นข้อผิดพลาดของการสร้างนี้:


โปรดทราบว่าคุณยังสามารถสร้างตัวชี้ "type safe" ให้กับอาร์เรย์ขนาดที่กำหนดได้เช่นนี้

... แต่ฉันไม่จำเป็นต้องแนะนำสิ่งนี้เพราะมันทำให้ฉันนึกถึงการแสดงตลก C ++ จำนวนมากที่ใช้ในการบังคับความปลอดภัยของประเภททุกที่ด้วยต้นทุนที่สูงเป็นพิเศษสำหรับความซับซ้อนของไวยากรณ์ภาษาความฟุ่มเฟือยและความยากลำบากในการออกแบบโค้ดซึ่งฉันไม่ชอบและ เคยพูดคุยกันหลายครั้งก่อนหน้านี้ (เช่นดู"My Thoughts on C ++" ที่นี่ )


สำหรับการทดสอบและการทดลองเพิ่มเติมโปรดดูที่ลิงค์ด้านล่าง

อ้างอิง

ดูลิงก์ด้านบน นอกจากนี้:

  1. การทดลองโค้ดของฉันออนไลน์: https://onlinegdb.com/B1RsrBDFD

2
void arraytest(int (*a)[1000])จะดีกว่าเพราะคอมไพเลอร์จะผิดพลาดหากขนาดไม่ถูกต้อง
Winger Sendon

@WingerSendon ฉันรู้ว่ามีรายละเอียดปลีกย่อยบางอย่างที่ฉันต้องตรวจสอบที่นี่และไวยากรณ์นั้นสับสน (เช่นไวยากรณ์ของฟังก์ชัน ptr จะสับสน) ดังนั้นฉันจึงใช้เวลาและอัปเดตคำตอบของฉันในที่สุดด้วยหัวข้อใหม่ขนาดใหญ่ที่Forcing type safety on arrays in Cครอบคลุม จุด.
Gabriel Staples

@ GabrielStaples ขอบคุณ คำตอบของคุณมีประโยชน์มาก คุณช่วยอ้างอิงถึงการเรียนรู้ c ขั้นสูงด้วยวิธีนี้ได้ไหม
daryooosh

@daryooosh น่าเสียดายที่ฉันทำไม่ได้ ฉันไม่มีข้อมูลอ้างอิงที่ยอดเยี่ยม ฉันได้หยิบมันขึ้นมาที่นี่เล็กน้อยที่นั่นโดยการขุดลึกลงไปเป็นเวลาหลายปี สิ่งที่ดีที่สุดที่ฉันทำได้คือบอกคุณเป็นครั้งคราวว่าฉันทิ้งสิ่งที่เรียนรู้แบบนี้ลงในeRCaGuy_hello_world repo ของฉันที่นี่เป็นครั้งคราว โปรดทราบว่าควรใช้อุปกรณ์ความปลอดภัยประเภท C ที่ฉันใช้ข้างต้นให้มากที่สุด มันจะทำให้โค้ดของคุณซับซ้อนและลดความสามารถในการอ่านได้เป็นตันและไม่คุ้มค่า มุ่งเน้นไปที่ไวยากรณ์ง่ายๆที่สามารถและทำให้สิ่งต่างๆอ่านได้
Gabriel Staples

หมายเหตุยังเป็นที่ยอมรับคลาสสิก C ตำรานี้คือ K & R การเขียนโปรแกรมภาษา Cหนังสือ: en.wikipedia.org/wiki/The_C_Programming_Language
Gabriel Staples

0

อาร์เรย์จะถูกส่งผ่านโดยการอ้างอิงเสมอหากคุณใช้a[]หรือ*a:


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