«указатель на указатель на первый индекс массива» - это неверное описание 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. Это содержит адрес массива указателей. Каждый из них указывает на какое-то место в памяти, содержащее текст соответствующего аргумента, который был передан в командной строке. Затем в конце этого массива гарантированно будет нулевой указатель.
Обратите внимание, что фактическое хранилище для отдельных аргументов, по крайней мере, потенциально выделяется отдельно друг от друга, поэтому их адреса в памяти могут быть расположены довольно случайно (но в зависимости от того, как происходит запись, они также могут находиться в одном непрерывном блоке память - вы просто не знаете и не должны заботиться).
Какой бы механизм компоновки не нарисовал для вас эту диаграмму, в их алгоритме минимизации пересечений есть ошибка!
Эрик Липперт
43
@EricLippert Можно было бы намеренно подчеркнуть, что пуанты не могут быть смежными или не в порядке.
Джеймсдлин
3
Я бы сказал, что это намеренно
Майкл
24
Это было определенно намеренно - и я думаю, что Эрик, вероятно, понял это, но (правильно, IMO) думал, что комментарий был забавным в любом случае.
Джерри Гроб
2
@JerryCoffin, можно также отметить, что даже если фактические аргументы были смежными в памяти, они могут иметь произвольную длину, поэтому для каждого из них по-прежнему требовались бы отдельные указатели, чтобы иметь возможность доступа argv[i]без сканирования через все предыдущие.
ilkkachu
22
Потому что это то, что обеспечивает операционная система :-)
Ваш вопрос немного о проблеме инверсии курицы / яйца. Проблема не в том, чтобы выбрать то, что вы хотите в C ++, а в том, как вы говорите в C ++, что дает вам ОС.
Unix передает массив «строк», каждая строка является аргументом команды. В C / C ++ строка - это "char *", поэтому массив строк - это char * argv [] или char ** argv, в зависимости от вкуса.
Нет, это именно «проблема выбора того, что вы хотите в 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-й элемент - так что (хотя это и не является строго необходимым - при условии, что строки являются смежными), это, как правило, намного удобнее.
если бы 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).
Вместо того, чтобы думать о нем как о «указателе на указатель», он помогает думать о нем как о «массиве строк», с []обозначением массива и char*обозначением строки. Когда вы запускаете программу, вы можете передать ей один или несколько аргументов командной строки, и они будут отражены в аргументах main: argcколичество аргументов, argvпозволяющее получить доступ к отдельным аргументам.
+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 ) - указатель на массив строк символов, которые содержат аргументы, по одному на строку.
char* argv[]
илиchar**
. Это указатель на указатель на символ; в частности, внешний указатель указывает на первый указатель в массиве, а внутренние указатели указывают на первые символы строк с нулевым символом в конце. Здесь нет индексов.char* argv[]
ставит пространство не в том месте. Скажемchar *argv[]
, и теперь ясно, что это означает, что «выражение*argv[n]
является переменной типаchar
». Не пытайтесь понять, что такое указатель, что указывает на указатель и так далее. Декларация говорит вам, какие операции вы можете выполнить с этой вещью.char * argv[]
с аналогичной конструкцией C ++std::string argv[]
, и, возможно, будет проще разобрать. ... Только не начинай писать так!char &func(int);
который не&func(5)
имеет типаchar
.Ответы:
Аргв в основном так:
Слева находится сам аргумент - что фактически передается в качестве аргумента в main. Это содержит адрес массива указателей. Каждый из них указывает на какое-то место в памяти, содержащее текст соответствующего аргумента, который был передан в командной строке. Затем в конце этого массива гарантированно будет нулевой указатель.
Обратите внимание, что фактическое хранилище для отдельных аргументов, по крайней мере, потенциально выделяется отдельно друг от друга, поэтому их адреса в памяти могут быть расположены довольно случайно (но в зависимости от того, как происходит запись, они также могут находиться в одном непрерывном блоке память - вы просто не знаете и не должны заботиться).
источник
argv[i]
без сканирования через все предыдущие.Потому что это то, что обеспечивает операционная система :-)
Ваш вопрос немного о проблеме инверсии курицы / яйца. Проблема не в том, чтобы выбрать то, что вы хотите в C ++, а в том, как вы говорите в C ++, что дает вам ОС.
Unix передает массив «строк», каждая строка является аргументом команды. В C / C ++ строка - это "char *", поэтому массив строк - это char * argv [] или char ** argv, в зависимости от вкуса.
источник
argv
массив - среда выполнения заботится о распределении командной строки и построенииargv
массива при запуске._start
который вызывает,main
просто нужно передатьmain
указатель на существующийargv
массив в памяти; это уже в правильном формате. Ядро копирует его из аргумента argv вexecve(const char *filename, char *const argv[], char *const envp[])
системный вызов, который был сделан для запуска нового исполняемого файла. (В Linux argv [] (сам массив) и argc находятся в стеке при входе в процесс. Я предполагаю, что большинство Unix-систем одинаковы, потому что это хорошее место для этого.)argc=2
и передачи всей плоской строки. (Следование букве стандарта недостаточно для того, чтобы быть полезным ; оно намеренно оставляет много места для выбора реализации.) Хотя некоторые программы Windows захотят обрабатывать кавычки специально, поэтому реальные реализации предоставляют способ получить плоскую строку, слишком.Во-первых, как объявление параметра, так
char **argv
же, какchar *argv[]
; они оба подразумевают указатель на (массив или набор из одного или нескольких возможных) указателей на строки.Далее, если у вас есть только «указатель на символ» - например, просто
char *
- тогда, чтобы получить доступ к n-му элементу, вам нужно будет отсканировать первые n-1 элементов, чтобы найти начало n-го элемента. (И это также наложило бы требование, чтобы каждая из строк сохранялась непрерывно.)С помощью массива указателей вы можете напрямую индексировать n-й элемент - так что (хотя это и не является строго необходимым - при условии, что строки являются смежными), это, как правило, намного удобнее.
Проиллюстрировать:
./program привет мир
Возможно, что в ОС предоставлен массив символов:
если бы argv был просто "указателем на символ", вы можете увидеть
Однако (хотя, скорее всего, по замыслу операционной системы), нет реальной гарантии, что три строки "./program", "hello" и "world" являются смежными. Кроме того, этот тип «одного указателя на несколько смежных строк» является более необычной конструкцией типа данных (для C), особенно по сравнению с массивом указателей на строку.
источник
argv --> "hello\0world\0"
вас естьargv --> index 0 of the array
(привет), как обычный массив. почему это не выполнимо? тогда вы продолжаете читать массивargc
раз. тогда вы передаете argv сам, а не указатель на argv.argv[4]
этоNULL
argv[argc] == NULL
. В этом случае этоargv[3]
не такargv[4]
.Возможный ответ заключается в том, что стандарт 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).источник
Вместо того, чтобы думать о нем как о «указателе на указатель», он помогает думать о нем как о «массиве строк», с
[]
обозначением массива иchar*
обозначением строки. Когда вы запускаете программу, вы можете передать ей один или несколько аргументов командной строки, и они будут отражены в аргументахmain
:argc
количество аргументов,argv
позволяющее получить доступ к отдельным аргументам.источник
char **
илиchar *[]
, что то же самое.Во многих случаях ответ «потому что это стандарт». Чтобы процитировать стандарт C99 :
Конечно, прежде чем он был стандартизирован это уже используется K & R C в ранних реализациях Unix, с целью сохранения параметров командной строки (то , что вы должны заботиться в Unix оболочки , такие как
/bin/bash
или ,/bin/sh
но не во встроенных системах). Процитирую первое издание K & R "The C Programming Language" (стр. 110) :источник