Чому C / C ++ main argv оголошено як "char * argv []", а не просто "char * argv"?


21

Чому argvоголошується як "вказівник на вказівник на перший індекс масиву", а не просто "вказівник на перший індекс масиву" ( char* argv)?

Чому тут потрібне поняття «вказівник на покажчик»?


4
"вказівник на вказівник на перший індекс масиву" - Це не правильний опис char* argv[]або char**. Це вказівник на вказівник на персонажа; конкретно зовнішній покажчик вказує на перший вказівник у масиві, а внутрішній покажчик вказує на перші символи нульових кінцевих рядків. Тут немає ніяких індексів.
Себастьян Редл

12
Як би ви отримали другий аргумент, якби це був просто char * argv?
gnasher729

15
Ваше життя стане простішим, коли ви поставите місце в потрібне місце. char* argv[]ставить простір у неправильне місце. Скажіть char *argv[], і тепер зрозуміло, що це означає "вираз *argv[n]є змінною типу char". Не зациклюйтеся на спробі розробити, що вказує, що таке вказівник, і так далі. У декларації йдеться про те, які операції ви можете виконати над цією справою.
Ерік Ліпперт

1
Подумки порівнюйте char * argv[]аналогічну конструкцію C ++ std::string argv[], і може бути простіше розібратися. ... Тільки не починайте насправді писати це саме так!
Час Джастіна - відновіть Моніку

2
@EricLippert зауважте, що питання також включає C ++, і там ви можете мати, наприклад, char &func(int);який не &func(5)має типу char.
Руслан

Відповіді:


59

В основному Argv такий:

введіть тут опис зображення

Ліворуч - сам аргумент - те, що насправді передається як аргумент до основного. Він містить адресу масиву покажчиків. Кожен із них вказує на якесь місце в пам'яті, що містить текст відповідного аргументу, переданий у командному рядку. Тоді в кінці цього масиву гарантовано буде нульовий покажчик.

Зауважте, що власне сховище для окремих аргументів принаймні потенційно виділяється окремо один від одного, тому їх адреси в пам'яті можуть бути розташовані досить випадковим чином (але залежно від того, як записуються речі, вони також можуть знаходитися в одному суміжному блоці пам'ять - ви просто не знаєте і не повинні турбуватися).


52
Який би механізм компонування не намалював цю діаграму, має помилку в алгоритмі мінімізації перетину!
Ерік Ліпперт

43
@EricLippert Неможливо навмисно підкреслити, що пуанси можуть бути не суміжними, ні в порядку.
jamesdlin

3
Я б сказав, що це навмисно
Майкл

24
Безумовно, це було навмисно - і я б припустив, що Ерік, напевно, подумав про це, але (правильно, IMO) вважав, що коментар у будь-якому випадку є смішним.
Джеррі Труну

2
@JerryCoffin, можна також зазначити, що навіть якщо фактичні аргументи були суміжними в пам'яті, вони можуть мати довільну довжину, тому все одно знадобляться чіткі вказівники для кожного з них, щоб мати доступ argv[i]без сканування через усі попередні.
ilkkachu

22

Тому що це забезпечує операційна система :-)

Ваше запитання полягає в питанні інверсії курки / яєць. Проблема полягає не в тому, щоб вибрати те, що ви хочете в C ++, проблема полягає в тому, як ви говорите на C ++ те, що дає вам ОС.

Unix передає масив "рядків", кожен рядок є аргументом команди. У C / C ++ рядок є "char *", тому масив рядків - char * argv [], або char ** argv, за смаком.


13
Ні, саме "проблема вибрати те, що ви хочете в C ++". Наприклад, Windows надає командний рядок як єдиний рядок, і все ж програми C / C ++ все ще отримують свій argvмасив - під час виконання роботи береться за токенізація командного рядка та побудова argvмасиву при запуску.
Joker_vD

14
@Joker_vD Я думаю , що в скрученому чином це про те, що операційна система дає Вам. Конкретніше: я думаю, що C ++ зробив це так, оскільки C зробив це так, а C зробив це так, оскільки в той час C і Unix були настільки нерозривно пов'язані, і Unix зробив це так.
Даніель Вагнер

1
@DanielWagner: Так, це з спадщини Unix C. У Unix / Linux мінімум, _startщо викликає mainпросто необхідність передати mainвказівник на існуючий argvмасив у пам'яті; це вже в потрібному форматі. Ядро копіює його з аргументу argv в execve(const char *filename, char *const argv[], char *const envp[])системний виклик, який було зроблено для запуску нового виконуваного файлу. (В Linux аргументи [] (сам масив) і argc знаходяться в стеці при введенні процесу. Я припускаю, що більшість Unixes однакові, тому що це гарне місце для цього.)
Пітер Кордес,

8
Але суть Джокера тут полягає в тому, що стандарти C / C ++ залишають його до реалізації, звідки беруться аргументи; вони не повинні бути прямо з ОС. Для ОС, що передає плоску рядок, хороша реалізація C ++ повинна включати токенізацію, а не встановлення argc=2та передачу всієї плоскої рядки. (Після букви цього стандарту недостатньо, щоб бути корисним ; це навмисно залишає багато можливостей для вибору варіантів впровадження.) Хоча деякі програми Windows хочуть спеціально ставитись до котирувань, тому реальні реалізації дійсно забезпечують спосіб отримати рівний рядок, теж.
Пітер Кордес

1
Відповідь Басіле в значній мірі - це виправлення + @ Джокера та мої коментарі з більш детальною інформацією.
Пітер Кордес

15

По-перше, як оголошення параметра char **argvє таким же, як char *argv[]; вони обидва мають на увазі вказівник на (масив або набір одного або декількох можливих) покажчиків на рядки.

Далі, якщо у вас є лише "покажчик на char" - наприклад, просто char *- тоді, щоб отримати доступ до n-го елемента, вам доведеться просканувати перші n-1 пункти, щоб знайти початок n-го елемента. (І це також накладе вимогу, щоб кожна з рядків зберігалася безперервно.)

За допомогою масиву покажчиків ви можете безпосередньо індексувати n-й елемент - так (хоча це не суворо необхідно - якщо вважати, що рядки є суміжними), як правило, набагато зручніше.

Проілюструвати:

./програма привіт світ

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

Можливо, що в OS надано масив символів:

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

якби аргв був лише "покажчиком на графік", ви можете побачити

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

Однак (хоча ймовірно за задумом ОС) немає реальної гарантії того, що три рядки "./program", "hello" та "world" є суміжними. Крім того, цей вид "єдиного вказівника на кілька суміжних рядків" є більш незвичною конструкцією типу даних (для C), особливо порівняно з масивом покажчиків на рядок.


що, якщо замість цього, у argv --> "hello\0world\0"вас є argv --> index 0 of the array(привіт), як звичайний масив. чому це не можливо? то ви продовжуєте читати масив argcразів. тоді ви передаєте сам argv, а не вказівник на argv.
користувач

@auser, ось що argv -> "./program\0hello\0\world\0" є: вказівник на перший знак (тобто ".") Якщо ви введете цей покажчик повз перший \ 0, то ви мати покажчик на "привіт \ 0", а після цього на "світ \ 0". Після аргументських разів (натискання \ 0 "), ви закінчили. Звичайно, це може бути
змушене

Ви забули сказати, що у вашому прикладі argv[4]єNULL
Василь Старинкевич,

3
Є гарантія, що (принаймні спочатку) argv[argc] == NULL. У цьому випадку це argv[3]не так argv[4].
Мірал

1
@Hill, так, дякую, оскільки я намагався бути явним щодо нульових термінаторів (і пропустив цей).
Ерік Ейдт

13

Чому C / C ++ main argv оголошено "char * argv []"

Можлива відповідь тому, що стандарт C11 n1570§5.1.2.2.1 запуск програми ) та C ++ 11 стандарт n3337головній функції § 3.6.6 ) вимагають цього для середовищ, що розміщуються (але зауважте, що стандарт C зазначає також §5.1.2.1 вільно розташовані середовища ) Дивіться також це .

Наступне питання: чому стандарти C та C ++ вирішили mainмати такий int main(int argc, char**argv)підпис? Пояснення багато в чому історичне: C був винайдений з Unix , який має оболонку, яка виконує глобалізацію перед тим, як робити fork(що є системним викликом для створення процесу) і execve(що є системним викликом для виконання програми), і execveпередає масив аргументів рядкових програм і пов'язаний mainз виконаною програмою. Детальніше про філософію Unix та про ABI s.

І C ++ намагався дотримуватися конвенцій C та бути сумісним з ним. Це не могло б визначити mainнесумісне з традиціями С.

Якщо ви спроектували операційну систему з нуля (все ще має інтерфейс командного рядка) та мову програмування для неї з нуля, ви зможете вигадувати різні умови запуску програми. І інші мови програмування (наприклад, звичайні Lisp, Ocaml або Go) мають різні умови початку програми.

На практиці mainвикликається деяким кодом crt0 . Зауважте, що в Windows глобалізація може здійснюватися кожною програмою в еквіваленті crt0, і деякі програми Windows можуть запускатися через нестандартну точку входу WinMain . У Unix глобалізація виконується оболонкою (і crt0адаптує ABI та початковий макет стека викликів, який він вказав, до умовних вимог вашої реалізації 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, з метою збереження параметрів командного рядка (то , що ви повинні піклуватися в Unix оболонки , такі як /bin/bashабо , /bin/shале не у вбудованих системах). Цитую е IRST видання K & R в «Мова програмування C» (стор 110.) :

Перший (умовно називається argc ) - це кількість аргументів командного рядка, до яких викликалася програма; другий ( argv ) - вказівник на масив символьних рядків, що містять аргументи, по одному на рядок.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.