เหตุใด argv หลักของ C / C ++ จึงถูกประกาศเป็น "char * argv []" แทนที่จะเป็น "char * argv"


21

เหตุใดจึงถูกargvประกาศให้เป็น "ตัวชี้ไปยังตัวชี้ไปยังดัชนีแรกของอาร์เรย์" แทนที่จะเป็น "ตัวชี้ไปยังดัชนีแรกของอาร์เรย์" ( char* argv)

เหตุใดจึงต้องใช้แนวคิดของ "ตัวชี้ไปยังตัวชี้"


4
"ตัวชี้ไปยังตัวชี้ไปยังดัชนีแรกของอาร์เรย์" - นั่นคือไม่ได้เป็นคำอธิบายที่ถูกต้องของหรือchar* argv[] char**นั่นคือตัวชี้ไปยังตัวชี้ไปยังตัวละคร โดยเฉพาะจุดตัวชี้ด้านนอกชี้ไปที่ตัวชี้แรกในอาร์เรย์และตัวชี้ภายในชี้ไปที่อักขระตัวแรกของสตริงที่สิ้นสุดด้วย nul ไม่มีดัชนีที่เกี่ยวข้องที่นี่
เซบาสเตียนเรดล

12
คุณจะรับอาร์กิวเมนต์ที่สองได้อย่างไรถ้ามันเป็นเพียง char * argv?
gnasher729

15
ชีวิตของคุณจะง่ายขึ้นเมื่อคุณวางพื้นที่ในตำแหน่งที่ถูกต้อง char* argv[]วางพื้นที่ในตำแหน่งที่ผิด พูดchar *argv[]และตอนนี้มันชัดเจนว่านี่หมายถึง "การแสดงออก*argv[n]เป็นตัวแปรประเภทchar" อย่าพลาดในการพยายามหาว่าตัวชี้คืออะไรและตัวชี้ไปยังตัวชี้เป็นต้น การประกาศกำลังบอกคุณว่าการดำเนินการที่คุณสามารถทำได้กับสิ่งนี้คืออะไร
Eric Lippert

1
เปรียบเทียบทางจิตchar * argv[]กับโครงสร้าง C ++ ที่คล้ายกันstd::string argv[]และอาจแยกวิเคราะห์ได้ง่ายขึ้น ... อย่าเพิ่งเริ่มต้นเขียนมันแบบนั้นจริงๆ!
Justin เวลา 2 Reinstate Monica

2
@EricLippert โปรดทราบว่าคำถามนี้ยังรวมถึง C ++ และมีคุณสามารถมีเช่นchar &func(int);ซึ่งไม่ได้ทำให้มีประเภท&func(5) char
Ruslan

คำตอบ:


59

Argv นั้นเป็นแบบนี้:

ป้อนคำอธิบายรูปภาพที่นี่

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

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


52
อะไรก็ตามที่เลย์เอาต์ของเครื่องยนต์ดึงไดอะแกรมนั้นมาให้คุณมีข้อบกพร่องในอัลกอริธึมการย่อตัวข้าม
Eric Lippert

43
@EricLippert อาจมีจุดประสงค์เพื่อเน้นว่าผู้ชี้ขาดอาจไม่ต่อเนื่องหรือเป็นระเบียบ
jamesdlin

3
ฉันจะบอกว่ามันเป็นความตั้งใจ
Michael

24
มันเป็นความตั้งใจอย่างแน่นอน - และฉันเดาว่า Eric อาจจะคิดว่า แต่ (ถูกต้อง IMO) คิดว่าความคิดเห็นนั้นตลกดีอยู่ดี
Jerry Coffin

2
@JerryCoffin หนึ่งยังอาจชี้ให้เห็นว่าแม้ว่าข้อโต้แย้งที่เกิดขึ้นจริงได้ที่ต่อเนื่องกันในหน่วยความจำที่พวกเขาสามารถมีความยาวโดยพลการอย่างใดอย่างหนึ่งเพื่อให้ยังคงต้องชี้แตกต่างกันสำหรับแต่ละของพวกเขาเพื่อให้สามารถเข้าถึงargv[i]ได้โดยไม่ต้องสแกนผ่านทุกคนก่อนหน้านี้
ilkkachu

22

เพราะนั่นคือสิ่งที่ระบบปฏิบัติการให้ :-)

คำถามของคุณเป็นปัญหาการผกผันไก่ / ไข่เล็กน้อย ปัญหาคือการไม่เลือกสิ่งที่คุณต้องการใน C ++ ปัญหาคือวิธีที่คุณพูดใน C ++ ว่าระบบปฏิบัติการให้อะไรคุณ

Unix ผ่านอาร์เรย์ของ "สตริง" แต่ละสตริงเป็นอาร์กิวเมนต์คำสั่ง ใน C / C ++ สตริงเป็น "char *" ดังนั้นอาร์เรย์ของสตริงจึงเป็นถ่าน * argv [] หรือถ่าน ** argv ตามรสนิยม


13
ไม่เป็น "ปัญหาในการเลือกสิ่งที่คุณต้องการใน C ++" ยกตัวอย่างเช่น Windows ให้บรรทัดคำสั่งเป็นสตริงเดียวและโปรแกรม C / C ++ ยังคงได้รับargvอาร์เรย์ - รันไทม์จะดูแล tokenizing บรรทัดคำสั่งและสร้างargvอาร์เรย์เมื่อเริ่มต้น
Joker_vD

14
@Joker_vD ผมคิดว่าในทางที่บิดมันเป็นเกี่ยวกับสิ่งที่ระบบปฏิบัติการจะช่วยให้คุณ โดยเฉพาะ: ฉันเดาว่า C ++ ทำอย่างนี้เพราะ C ทำอย่างนี้และ C ทำอย่างนี้เพราะในเวลา C และ Unix เชื่อมโยงกันอย่างแยกไม่ออกและ Unix ก็ทำเช่นนี้
Daniel Wagner

1
@DanielWagner: ใช่นี่มาจากมรดก Unix ของ C บน Unix / Linux เพียงเล็กน้อย_startที่การโทรmainต้องผ่านmainตัวชี้ไปยังargvอาร์เรย์ที่มีอยู่ในหน่วยความจำ มันอยู่ในรูปแบบที่ถูกต้องแล้ว เคอร์เนลคัดลอกจากอาร์กิวเมนต์ argv ไปยังการexecve(const char *filename, char *const argv[], char *const envp[])เรียกระบบที่สร้างขึ้นเพื่อเริ่มปฏิบัติการใหม่ (บน Linux argv [] (อาร์เรย์เอง) และ argc อยู่บนสแต็คในรายการกระบวนการผมถือว่า Unixes มากที่สุดเหมือนกันเพราะเห็นว่าเป็นสถานที่ที่ดีสำหรับมัน..)
ปีเตอร์ Cordes

8
แต่ประเด็นของโจ๊กเกอร์ที่นี่คือมาตรฐาน C / C ++ ปล่อยให้มันขึ้นอยู่กับการใช้งานที่มาจาก args; พวกเขาไม่จำเป็นต้องตรงจากระบบปฏิบัติการ บนระบบปฏิบัติการที่ส่งผ่านสตริงแบบแบนการใช้งาน C ++ ที่ดีควรรวม tokenizing แทนการตั้งค่าargc=2และส่งผ่านสตริงแบบแบนทั้งหมด (ตามตัวอักษรของมาตรฐานไม่เพียงพอที่จะเป็นประโยชน์มันจงใจออกจากห้องมากมายสำหรับตัวเลือกการใช้งาน) แม้ว่าบางโปรแกรม Windows จะต้องการรักษาอัญประกาศเป็นพิเศษดังนั้นการใช้งานจริงมีวิธีรับสตริงแบน เกินไป.
Peter Cordes

1
คำตอบของ Basileนั้นเป็นการแก้ไขของ + @ Joker และความคิดเห็นของฉันพร้อมรายละเอียดเพิ่มเติม
Peter Cordes

15

ก่อนอื่นเป็นการประกาศพารามิเตอร์char **argvเหมือนกับchar *argv[]; พวกเขาทั้งสองบ่งบอกถึงตัวชี้ไปที่ (อาร์เรย์หรือชุดของหนึ่งหรือมากกว่าเป็นไปได้) ตัวชี้ไปยังสตริง

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

ด้วยอาเรย์ของพอยน์เตอร์คุณสามารถสร้างดัชนีรายการที่ n ได้โดยตรง - ดังนั้น (โดยไม่จำเป็นอย่างยิ่ง - สมมติว่าสตริงนั้นต่อเนื่องกัน) โดยทั่วไปจะสะดวกกว่ามาก

เพื่อแสดง:

./ โปรแกรมสวัสดีชาวโลก

argc = 3
argv[0] --> "./program\0"
argv[1] --> "hello\0"
argv[2] --> "world\0"

เป็นไปได้ว่าในระบบปฏิบัติการที่จัดเตรียมอาร์เรย์ของอักขระ:

            "./program\0hello\0world\0"
argv[0]      ^
argv[1]                 ^
argv[2]                        ^

ถ้า argv เป็นเพียง "ตัวชี้ไปยังถ่าน" คุณอาจเห็น

       "./program\0hello\0world\0"
argv    ^

อย่างไรก็ตาม (อาจเป็นไปได้จากการออกแบบระบบปฏิบัติการ) ไม่มีการรับประกันจริง ๆ ว่าสามสาย "./program", "hello" และ "โลก" นั้นต่อเนื่องกัน นอกจากนี้ "ตัวชี้เดียวกับสตริงที่ต่อเนื่องกันหลายชนิด" นี้เป็นโครงสร้างข้อมูลประเภทที่ผิดปกติมากขึ้น (สำหรับ C) โดยเฉพาะอย่างยิ่งเมื่อเปรียบเทียบกับอาร์เรย์ของตัวชี้ไปยังสตริง


ถ้าเป็นเช่นนั้นargv --> "hello\0world\0"คุณจะมีargv --> index 0 of the array(สวัสดี) เหมือนกับอาร์เรย์ปกติ ทำไมสิ่งนี้ถึงไม่สามารถทำได้ จากนั้นคุณอ่านอาเรย์argcครั้งต่อไป จากนั้นคุณจะส่ง argv เองและไม่ใช่ตัวชี้ไปยัง argv
ผู้ใช้

@auser นั่นคือสิ่งที่ argv -> "./program\0hello\0\world\0" คือ: ตัวชี้ไปที่อักขระตัวแรก (เช่น ".") หากคุณใช้ตัวชี้นั้นผ่าน \ 0 ตัวแรกคุณก็จะได้ มีตัวชี้ไปที่ "hello \ 0" และหลังจากนั้นถึง "world \ 0" หลังจากเวลา argc (กดปุ่ม \ 0 ") คุณทำเสร็จแล้วแน่นอนมันสามารถทำงานได้และอย่างที่ฉันบอกว่าเป็นโครงสร้างที่ผิดปกติ
Erik Eidt

คุณลืมที่จะระบุว่าในตัวอย่างของคุณargv[4]คือNULL
Basile Starynkevitch

3
มีการรับประกันว่า argv[argc] == NULL(อย่างน้อยในตอนแรก) ในกรณีนี้ที่ไม่argv[3] argv[4]
Miral

1
@ ฮิลล์ใช่ขอบคุณขอบคุณที่ฉันพยายามที่จะอธิบายเกี่ยวกับตัวสิ้นสุดของตัวละครโมฆะ (และพลาดไป)
Erik Eidt

13

เหตุใด C / C ++ main argv จึงถูกประกาศเป็น“ char * argv []”

คำตอบที่เป็นไปได้คือเพราะมาตรฐาน C11 n1570 (ในstartup5.1.2.2.1 การเริ่มต้นโปรแกรม ) และ C ++ 11 มาตรฐาน n3337 (ในฟังก์ชั่นหลัก§3.6.1 ) ต้องการสิ่งนั้นสำหรับสภาพแวดล้อมที่โฮสต์ (แต่สังเกตว่า C มาตรฐานกล่าวถึง ยัง§5.1.2.1สภาพแวดล้อมอิสระ ) ดูสิ่งนี้ด้วย

คำถามต่อไปคือทำไมมาตรฐาน C และ C ++ จึงเลือกที่mainจะมีint main(int argc, char**argv)ลายเซ็นดังกล่าว คำอธิบายที่เป็นประวัติศาสตร์ส่วนใหญ่: Cถูกคิดค้นกับUnixซึ่งมีเปลือกซึ่งไม่globbingก่อนที่จะทำfork(ซึ่งเป็นสายระบบการสร้างกระบวนการ) และexecve(ซึ่งเป็นสายระบบการรันโปรแกรม) และที่execveถ่ายทอดอาร์เรย์ ของอาร์กิวเมนต์โปรแกรมสตริงและเกี่ยวข้องกับmainโปรแกรมที่ดำเนินการ อ่านเพิ่มเติมเกี่ยวกับปรัชญา UnixและABI s

และ C ++ พยายามอย่างหนักที่จะทำตามอนุสัญญา C และเข้ากันได้กับมัน ไม่สามารถระบุmainว่าขัดกับประเพณี C ได้

หากคุณออกแบบระบบปฏิบัติการตั้งแต่เริ่มต้น (ยังคงมีอินเตอร์เฟสบรรทัดคำสั่ง) และภาษาการเขียนโปรแกรมตั้งแต่เริ่มต้นคุณจะมีอิสระในการคิดค้นโปรแกรมที่แตกต่างกันโดยเริ่มจากอนุสัญญา และภาษาการเขียนโปรแกรมอื่น ๆ (เช่น Common Lisp หรือ Ocaml หรือ Go) มีข้อกำหนดการเริ่มต้นโปรแกรมที่แตกต่างกัน

ในทางปฏิบัติmainถูกเรียกใช้โดยรหัสcrt0 ขอให้สังเกตว่าบน Windows globbing อาจทำได้โดยแต่ละโปรแกรมในเทียบเท่า crt0 และบางโปรแกรมของ Windows สามารถเริ่มต้นผ่านไม่ได้มาตรฐานจุดเข้า WinMain บน Unix นั้น globbing นั้นทำโดยเชลล์ (และcrt0กำลังปรับ ABI และโครงร่าง call stack เริ่มต้นที่มันได้ระบุไว้ไปยังการเรียกแบบแผนของการติดตั้ง C ของคุณ)


12

แทนที่จะคิดว่ามันเป็น "ตัวชี้ไปยังตัวชี้" มันช่วยให้คิดว่ามันเป็น "อาเรย์ของสตริง" โดยมีการ[]แสดงอาเรย์และchar*สตริงที่แสดงถึง เมื่อคุณเรียกใช้โปรแกรมคุณสามารถส่งผ่านอาร์กิวเมนต์บรรทัดคำสั่งหนึ่งรายการขึ้นไปและสิ่งเหล่านี้จะสะท้อนให้เห็นในอาร์กิวเมนต์ถึงmain: argcคือจำนวนของอาร์กิวเมนต์และargvให้คุณเข้าถึงอาร์กิวเมนต์แต่ละรายการ


2
+1 นี่! ในหลายภาษา - bash, PHP, C, C ++ - argv เป็นอาร์เรย์ของสตริง จากนี้คุณต้องคิดว่าเมื่อคุณเห็นchar **หรือchar *[]ที่เหมือนกัน
rexkogitans

1

ในหลายกรณีคำตอบคือ "เพราะเป็นมาตรฐาน" ในการอ้างอิงมาตรฐาน C99 :

- หากค่าของ argc มากกว่าศูนย์สมาชิกอาเรย์ argv [0] ถึง argv [argc-1] รวมจะต้องมีตัวชี้ไปยังสตริงซึ่งจะได้รับค่ากำหนดการดำเนินงานโดยสภาพแวดล้อมของโฮสต์ก่อนที่จะเริ่มโปรแกรม

แน่นอนก่อนที่จะได้รับมาตรฐานมันมีอยู่แล้วในการใช้งานโดย K & R C ในช่วงต้นการใช้งานระบบปฏิบัติการยูนิกซ์มีวัตถุประสงค์ในการจัดเก็บค่าพารามิเตอร์บรรทัดคำสั่ง (สิ่งที่คุณจะต้องดูแลใน Unix เปลือกเช่น/bin/bashหรือ/bin/shแต่ไม่ได้อยู่ในระบบฝังตัว) หากต้องการอ้างถึง "The C Programming Language" รุ่นแรกของ K&R (หน้า 110) :

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

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