Mengapa C / C ++ argumen utama dinyatakan sebagai "char * argv []" daripada hanya "char * argv"?

21

Mengapa argvdinyatakan sebagai "pointer ke pointer ke indeks array pertama", bukan hanya menjadi "pointer ke indeks array pertama" ( char* argv)?

Mengapa gagasan "pointer ke pointer" diperlukan di sini?

pengguna
sumber
4
"pointer ke pointer ke indeks pertama array" - Itu bukan deskripsi char* argv[]atau char**. Itu adalah pointer ke pointer ke karakter; khususnya pointer luar menunjuk ke pointer pertama dalam sebuah array, dan pointer batin menunjuk ke karakter pertama dari string nul-terminated. Tidak ada indeks yang terlibat di sini.
Sebastian Redl
12
Bagaimana Anda mendapatkan argumen kedua jika itu hanya char * argv?
gnasher729
15
Hidup Anda akan semakin mudah ketika Anda menempatkan ruang di tempat yang tepat. char* argv[]menempatkan ruang di tempat yang salah. Katakan char *argv[], dan sekarang jelas bahwa ini berarti "ekspresi *argv[n]adalah variabel tipe char". Jangan terjebak dalam mencoba mencari tahu apa itu pointer dan apa itu pointer ke pointer, dan sebagainya. Deklarasi memberi tahu Anda operasi apa yang dapat Anda lakukan untuk hal ini.
Eric Lippert
1
Secara mental dibandingkan char * argv[]dengan konstruksi C ++ yang serupa std::string argv[], dan mungkin lebih mudah untuk diuraikan. ... Hanya saja, jangan mulai menulis seperti itu!
Justin Time 2 Reinstate Monica
2
@EricLippert perhatikan bahwa pertanyaannya juga termasuk C ++, dan di sana Anda dapat memiliki mis char &func(int);yang tidak membuat &func(5)memiliki tipe char.
Ruslan

Jawaban:

59

Argv pada dasarnya seperti ini:

masukkan deskripsi gambar di sini

Di sebelah kiri adalah argumen itu sendiri - apa yang sebenarnya dilewatkan sebagai argumen ke utama. Itu berisi alamat dari array pointer. Masing-masing menunjuk ke suatu tempat di memori yang berisi teks dari argumen yang sesuai yang dilewatkan pada baris perintah. Kemudian, di akhir array itu dijamin ada pointer nol.

Perhatikan bahwa penyimpanan aktual untuk argumen individual setidaknya berpotensi dialokasikan secara terpisah dari satu sama lain, sehingga alamat mereka dalam memori dapat diatur secara acak (tetapi tergantung pada bagaimana hal-hal yang akan ditulis, mereka juga bisa berada dalam satu blok yang berdekatan dari memori - Anda tidak tahu dan tidak peduli).

Jerry Coffin
sumber
52
Mesin tata letak apa pun yang menggambar diagram itu bagi Anda memiliki bug dalam algoritma minim-penyilangan mereka!
Eric Lippert
43
@EricLippert Bisa disengaja untuk menekankan bahwa poinee mungkin tidak bersebelahan atau dalam urutan.
jamesdlin
3
Saya akan mengatakan itu disengaja
Michael
24
Itu memang disengaja - dan saya kira Eric mungkin mengetahuinya, tetapi (benar, IMO) menganggap komentar itu lucu.
Jerry Coffin
2
@ JerryCoffin, orang mungkin juga menunjukkan bahwa bahkan jika argumen yang sebenarnya bersebelahan dalam memori, mereka dapat memiliki panjang sewenang-wenang, jadi kita masih perlu pointer yang berbeda untuk masing-masing dari mereka untuk dapat mengakses argv[i]tanpa memindai melalui semua yang sebelumnya.
ilkkachu
22

Karena itulah yang disediakan sistem operasi :-)

Pertanyaan Anda adalah sedikit masalah inversi ayam / telur. Masalahnya bukan untuk memilih apa yang Anda inginkan dalam C ++, masalahnya adalah bagaimana Anda mengatakan dalam C ++ apa yang diberikan OS kepada Anda.

Unix melewati larik "string", setiap string menjadi argumen perintah. Dalam C / C ++, string adalah "char *", jadi array string adalah char * argv [], atau char ** argv, sesuai selera.

orang yang lewat
sumber
13
Tidak, ini justru "masalah untuk memilih apa yang Anda inginkan dalam C ++". Windows, misalnya, menyediakan baris perintah sebagai string tunggal, namun program C / C ++ masih menerima argvlariknya - runtime menangani tokenizing baris perintah dan membangun argvlarik pada saat startup.
Joker_vD
14
@ Joker_vD Saya pikir dengan cara memutar itu adalah tentang apa yang OS berikan kepada Anda. Khususnya: Saya kira C ++ melakukannya dengan cara ini karena C melakukannya dengan cara ini, dan C melakukannya dengan cara ini karena pada saat itu C dan Unix sangat terkait erat dan Unix melakukannya dengan cara ini.
Daniel Wagner
1
@DanielWagner: Ya, ini dari C's Unix heritage. Pada Unix / Linux, minimal _startyang memanggil mainhanya perlu melewati mainpointer ke argvarray yang ada di memori; sudah dalam format yang tepat. Kernel menyalinnya dari argumen argv ke execve(const char *filename, char *const argv[], char *const envp[])panggilan sistem yang dibuat untuk memulai executable baru. (Di Linux, argv [] (array itu sendiri) dan argc berada di stack pada proses entri. Saya berasumsi sebagian besar Unix adalah sama, karena itu adalah tempat yang bagus untuk itu.)
Peter Cordes
8
Tapi poin Joker di sini adalah bahwa standar C / C ++ menyerahkannya pada implementasi dari mana argumen tersebut berasal; mereka tidak harus langsung dari OS. Pada OS yang melewati string datar, baik C ++ pelaksanaan harus mencakup tokenizing, bukan pengaturan argc=2dan melewati string datar seluruh. (Mengikuti huruf standar tidak cukup untuk berguna ; itu sengaja menyisakan banyak ruang untuk pilihan implementasi.) Meskipun beberapa program Windows akan ingin memperlakukan penawaran secara khusus, sehingga implementasi nyata menyediakan cara untuk mendapatkan string datar, terlalu.
Peter Cordes
1
Jawaban Basile cukup banyak + ini koreksi @ Joker dan komentar saya, dengan rincian lebih lanjut.
Peter Cordes
15

Pertama, sebagai deklarasi parameter, char **argvsama dengan char *argv[]; keduanya menyiratkan pointer ke (array atau set satu atau lebih mungkin) pointer ke string.

Selanjutnya, jika Anda hanya memiliki "pointer to char" - misal saja char *- maka untuk mengakses item ke-n, Anda harus memindai item ke-1 pertama untuk menemukan awal item ke-n. (Dan ini juga akan memaksakan persyaratan bahwa masing-masing string disimpan secara berdekatan.)

Dengan array pointer, Anda dapat langsung mengindeks item ke-n - jadi (walaupun tidak sepenuhnya diperlukan - dengan asumsi string berdekatan) umumnya lebih mudah.

Menggambarkan:

./program halo dunia

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

Mungkin saja, dalam larik karakter yang disediakan os:

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

jika argv hanyalah "pointer to char" yang mungkin Anda lihat

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

Namun (walaupun kemungkinan berdasarkan desain os) tidak ada jaminan nyata bahwa ketiga string "./program", "hello", dan "world" bersebelahan. Lebih jauh, jenis "penunjuk tunggal ke banyak string yang berdekatan" ini adalah konstruk tipe data yang lebih tidak biasa (untuk C), terutama dibandingkan dengan array pointer ke string.

Erik Eidt
sumber
bagaimana jika alih-alih, argv --> "hello\0world\0"Anda memiliki argv --> index 0 of the array(halo), seperti array normal. mengapa ini tidak bisa dilakukan? maka Anda terus membaca waktu array argc. maka Anda melewatkan argv itu sendiri dan bukan pointer ke argv.
pengguna
@auser, itulah yang argv -> "./program\0hello\0\world "0 adalah: pointer ke char pertama (yaitu". ") Jika Anda mengambil pointer melewati \ \ pertama, maka Anda memiliki pointer ke "hello \ 0", dan setelah itu ke "world \ 0". Setelah bertengkar (memukul \ 0 "), Anda selesai. Tentu saja, itu dapat dibuat untuk bekerja, dan seperti yang saya katakan, sebuah konstruksi yang tidak biasa.
Erik Eidt
Anda lupa menyatakan bahwa dalam contoh Anda argv[4]adalahNULL
Basile Starynkevitch
3
Ada jaminan bahwa (setidaknya pada awalnya) argv[argc] == NULL. Dalam hal ini argv[3], bukan argv[4].
Miral
1
@Hill, ya, terima kasih karena saya mencoba untuk menjadi eksplisit tentang terminator karakter nol (dan melewatkan yang satu itu).
Erik Eidt
13

Mengapa C / C ++ argv utama dinyatakan sebagai “char * argv []”

Jawaban yang mungkin adalah karena standar C11 n1570 (dalam §5.1.2.2.1 Program startup ) dan standar C ++ 11 n3337 (dalam §3.6.1 fungsi utama ) mengharuskan untuk lingkungan yang dihosting (tetapi perhatikan bahwa standar C menyebutkan juga §5.1.2.1 lingkungan berdiri bebas ) Lihat juga ini .

Pertanyaan selanjutnya adalah mengapa standar C dan C ++ memilih mainuntuk memiliki int main(int argc, char**argv)tanda tangan seperti itu ? Penjelasannya sebagian besar historis: C ditemukan dengan Unix , yang memiliki shell yang melakukan globbing sebelum melakukan fork(yang merupakan panggilan sistem untuk membuat proses) dan execve(yang merupakan panggilan sistem untuk menjalankan program), dan yang execvementransmisikan array argumen program string dan terkait dengan mainprogram yang dijalankan. Baca lebih lanjut tentang filosofi Unix dan tentang ABI .

Dan C ++ berusaha keras untuk mengikuti konvensi C dan kompatibel dengannya. Itu tidak dapat didefinisikan mainsebagai tidak sesuai dengan tradisi C.

Jika Anda merancang sistem operasi dari awal (masih memiliki antarmuka baris perintah) dan bahasa pemrograman untuk itu dari awal, Anda akan bebas untuk membuat konvensi awal program yang berbeda. Dan bahasa pemrograman lain (misalnya Common Lisp atau Ocaml atau Go) memiliki konvensi awal program yang berbeda.

Dalam prakteknya, maindipanggil oleh beberapa kode crt0 . Perhatikan bahwa pada Windows globbing dapat dilakukan oleh setiap program setara dengan crt0, dan beberapa program Windows dapat mulai melalui titik masuk WinMain yang tidak standar . Di Unix, globbing dilakukan oleh shell (dan crt0mengadaptasi ABI, dan tata letak tumpukan panggilan awal yang telah ditentukan, untuk memanggil konvensi implementasi C Anda).

Basile Starynkevitch
sumber
12

Alih-alih menganggapnya sebagai "pointer to pointer", ada baiknya menganggapnya sebagai "array of strings", dengan []menunjukkan array dan char*menunjukkan string. Ketika Anda menjalankan suatu program, Anda bisa meneruskannya satu atau lebih argumen baris perintah dan ini tercermin dalam argumen untuk main: argcadalah jumlah argumen dan argvmemungkinkan Anda mengakses argumen individual.

casablanca
sumber
2
Beri ini +1! Dalam banyak bahasa - bash, PHP, C, C ++ - argv adalah array dari string. Tentang ini Anda harus berpikir ketika Anda melihat char **atau char *[], yang sama.
rexkogitans
1

Dalam banyak kasus jawabannya adalah "karena standar". Mengutip standar C99 :

- Jika nilai argc lebih besar dari nol, anggota array argv [0] melalui argv [argc-1] inklusif harus berisi pointer ke string , yang diberi nilai-nilai yang ditentukan implementasi oleh lingkungan host sebelum program startup.

Tentu saja, sebelum distandarisasi, sudah digunakan oleh K&R C dalam implementasi Unix awal, dengan tujuan menyimpan parameter baris perintah (sesuatu yang harus Anda perhatikan di shell Unix seperti /bin/bashatau /bin/shtidak di embedded system). Mengutip edisi pertama K&R "The C Programming Language" (hlm. 110) :

Yang pertama (secara konvensional disebut argc ) adalah jumlah argumen baris perintah yang digunakan oleh program; yang kedua ( argv ) adalah pointer ke array string karakter yang berisi argumen, satu per string.

Sergiy Kolodyazhnyy
sumber