Почему основной argv в C / C ++ объявлен как «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

Аргв в основном так:

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

Слева находится сам аргумент - что фактически передается в качестве аргумента в main. Это содержит адрес массива указателей. Каждый из них указывает на какое-то место в памяти, содержащее текст соответствующего аргумента, который был передан в командной строке. Затем в конце этого массива гарантированно будет нулевой указатель.

Обратите внимание, что фактическое хранилище для отдельных аргументов, по крайней мере, потенциально выделяется отдельно друг от друга, поэтому их адреса в памяти могут быть расположены довольно случайно (но в зависимости от того, как происходит запись, они также могут находиться в одном непрерывном блоке память - вы просто не знаете и не должны заботиться).


52
Какой бы механизм компоновки не нарисовал для вас эту диаграмму, в их алгоритме минимизации пересечений есть ошибка!
Эрик Липперт

43
@EricLippert Можно было бы намеренно подчеркнуть, что пуанты не могут быть смежными или не в порядке.
Джеймсдлин

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: Да, это из наследия C Unix. В Unix / Linux минимальному, _startкоторый вызывает, mainпросто нужно передать mainуказатель на существующий argvмассив в памяти; это уже в правильном формате. Ядро копирует его из аргумента argv в execve(const char *filename, char *const argv[], char *const envp[])системный вызов, который был сделан для запуска нового исполняемого файла. (В Linux argv [] (сам массив) и argc находятся в стеке при входе в процесс. Я предполагаю, что большинство Unix-систем одинаковы, потому что это хорошее место для этого.)
Peter Cordes

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

1
Ответ Basile - это, в основном, исправление + @ Joker и мои комментарии, с более подробной информацией.
Питер Кордес

15

Во-первых, как объявление параметра, так char **argvже, как char *argv[]; они оба подразумевают указатель на (массив или набор из одного или нескольких возможных) указателей на строки.

Далее, если у вас есть только «указатель на символ» - например, просто char *- тогда, чтобы получить доступ к n-му элементу, вам нужно будет отсканировать первые n-1 элементов, чтобы найти начало n-го элемента. (И это также наложило бы требование, чтобы каждая из строк сохранялась непрерывно.)

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

Проиллюстрировать:

./program привет мир

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" и "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". После времени argc (нажатие \ 0 ") все готово. Конечно, его можно
заставить

Вы забыли заявить, что в вашем примере argv[4]этоNULL
Василий Старынкевич

3
Есть гарантия, что (хотя бы изначально) argv[argc] == NULL. В этом случае это argv[3]не так argv[4].
Мирал

1
@Hill, да, спасибо, я пытался быть откровенным о терминаторах нулевых символов (и пропустил это).
Эрик Эйдт

13

Почему C / C ++ основной argv объявлен как «char * argv []»

Возможный ответ заключается в том, что стандарт C11 n1570§5.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 , у которого есть оболочка, которая выполняет глобализацию перед выполнением fork(это системный вызов для создания процесса) и execve(который является системным вызовом для выполнения программы) и которая execveпередает массив строковых аргументов программы и относится к mainисполняемой программе. Узнайте больше о философии Unix и о ABI .

И C ++ старался следовать соглашениям C и быть совместимым с ним. Он не может быть определен mainкак несовместимый с традициями Си.

Если вы разработали операционную систему с нуля (по-прежнему с интерфейсом командной строки) и язык программирования для нее с нуля, вы можете свободно придумывать различные соглашения о запуске программ. И другие языки программирования (например, Common 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но не во встроенных системах). Процитирую первое издание K & R "The C Programming Language" (стр. 110) :

Первый (условно называемый argc ) - это количество аргументов командной строки, с которыми была вызвана программа; второй ( argv ) - указатель на массив строк символов, которые содержат аргументы, по одному на строку.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.