Почему argv (вектор аргументов) в C определяется как указатель и зачем определять его ноль в качестве имени программы?

#include <stdio.h>
int main(int argc, char *argv[])
{
 int i;
 for(i=1;i<argc;i++)
  printf("%s%s", argv[i], (i<argc-1)? " ":"");
 printf("\n");
 return 0;
} 

Выше приведена простая программа на C, которая выводит входные данные командной строки. Здесь argc — счетчик аргументов. argv называется массивом, содержащим аргументы. Мой вопрос: почему он определяется как указатель на массив символов вместо обычного массива? Также зачем определять его нулевой элемент (argv[0]) как имя, по которому вызывается программа.

Я новичок, и, пожалуйста, объясните это с точки зрения высокого уровня.


person user3298129    schedule 12.02.2014    source источник
comment
Я имел в виду, что там нужен указатель.   -  person user3298129    schedule 12.02.2014
comment
На самом деле вчера вечером я разговаривал с другом о вашем втором вопросе - я сказал, что это может понадобиться, если пользователь изменит имя программы, нам может понадобиться знать, как она теперь называется, скажем, чтобы вызвать другой экземпляр, обновить , и т.д.   -  person OJFord    schedule 12.02.2014
comment
Было бы лучше назвать его массивом указателей на char, другими словами, если char * представляет строку в стиле C, то char *x[] означает, что x представляет собой массив строк в стиле C.   -  person Brandin    schedule 12.02.2014
comment
Чтобы ответить на ваш другой вопрос, запустите свой цикл из i=0, затем попробуйте вызвать свою команду из оболочки разными способами, например. ./foo bar baz или /path/to/foo bar baz и срок действия, чтобы увидеть, есть ли разница в том, как ваша среда передает argv[0]   -  person Brandin    schedule 12.02.2014
comment
@Brandin: Если char *x[] является объявлением параметра, это означает, что x является указателем на указатель на char. Он может указывать или не указывать на элемент массива char* указателей, и эти char* указатели могут указывать или не указывать на строки в стиле C; в объявлении параметра это не указано.   -  person Keith Thompson    schedule 12.02.2014
comment
@ Кейт Томпсон Да. Вот почему в объявлении можно написать эквивалентно char **x. Да. char * не подразумевает автоматически строку в стиле C. Кроме того, интересно отметить, что определение строк в стиле C не обеспечивает надежного способа проверки того, является ли что-либо строкой. Не уверен, что это в стандарте C или POSIX, но я думаю, что где-то указано, что в особом случае argv это должно содержать массив строк в стиле C, последний элемент которого имеет значение (char *)0 в соответствующих реализациях   -  person Brandin    schedule 12.02.2014
comment
Это все объясняет: youtube.com/watch?v=gRdfX7ut8gw   -  person Hot Licks    schedule 12.02.2014


Ответы (4)


argv определяется как указатель, а не как массив, потому что в C нет такой вещи, как параметр массива.

Вы можете определить что-то, что выглядит как параметр массива, но "приспосабливается" к типу массива во время компиляции; например, эти два объявления полностью эквивалентны:

int foo(int param[]);
int foo(int param[42]); /* the 42 is quietly ignored */
int foo(int *param);    /* this is what the above two declarations really mean */

И определение main можно записать так:

int main(int argc, char *argv[]) { /* ... */ }

or as

int main(int argc, char **argv) { /* ... */ }

Эти два точно эквивалентны (и второй, ИМХО, более четко выражает то, что на самом деле происходит).

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

Раздел 6 часто задаваемых вопросов о comp.lang.c объясняет часто запутанные отношения между массивами и указателями.

(И если вам сказали, что массивы «на самом деле» являются указателями, это не так; массивы и указатели — разные вещи.)

Что касается того, почему argv[0] указывает на имя программы, то это просто потому, что это полезно. Некоторые программы печатают свои имена в сообщениях об ошибках; другие могут изменить свое поведение в зависимости от имени, под которым они вызываются. Объединение имени программы с аргументами командной строки было довольно произвольным выбором, но это удобно и работает.

person Keith Thompson    schedule 12.02.2014

char *argv[] — это указатель, на который распался массив char *. Например, вызов такой команды:

$ ./command --option1 -opt2 input_file

можно рассматривать как:

char *argv[] = {
    "./command",
    "--option1",
    "-opt2",
    "input_file",
    NULL,
};
main(4, argv);

Таким образом, в основном существует массив строк за пределами main, и он передается вам в main:

    char *argv[]
    \- --/     ^
      V        |
      |   It was an array
      |
of strings

Причина, по которой argv[0] является командой вызова, в значительной степени историческая. Не знаю, о чем думал первый человек, который до этого додумался, но могу назвать по крайней мере одну полезность для него.

Представьте себе программу, например vim или gawk. Эти программы могут устанавливать символические ссылки (например, vi или awk), которые указывают на одну и ту же программу. Таким образом, запуск vim или vi (или аналогично gawk или awk) может выполнять одну и ту же программу. Однако, проверяя argv[0], эти программы могут определить, как они были вызваны, и, возможно, внести соответствующие коррективы.

Насколько мне известно, ни одна из упомянутых выше программ на самом деле этого не делает, но могла бы. Например, vim, вызываемый через символическую ссылку с именем vi, может включить некоторую совместимость. Или gawk, называемый awk, может отключить некоторые расширения GNU. Однако в современном мире, если бы они хотели это сделать, они, вероятно, создали бы скрипты, которые дают правильные варианты.

person Shahbaz    schedule 12.02.2014
comment
char *argv[] (который как параметр точно эквивалентен char**) не является указателем на массив char*; это указатель на первый элемент массива char*. Указатель на char* и указатель на массив char* будут иметь разные типы. - person Keith Thompson; 12.02.2014
comment
vim ведет себя по-разному в зависимости от того, вызывается ли он как vim, view, vimdiff, rview, rvim (и, возможно, как vi). - person Jonathan Leffler; 12.02.2014
comment
@KeithThompson, я сделал формулировку более точной. - person Shahbaz; 12.02.2014
comment
@JonathanLeffler, точно. Однако пару раз, когда я на самом деле писал vi вместо vim, я не видел никакой разницы, поэтому я сказал: Насколько мне известно, ни одна из упомянутых выше программ на самом деле этого не делает (относительно vim и vi) - person Shahbaz; 12.02.2014
comment
char *argv[] — это указатель, на который распался массив char *. -- Я бы так не сказал. Сущность, которая вызывает main, даже не обязательно написана на C. выражение массива распадается на указатель на его первый элемент, но не обязательно какое-либо выражение массива. Здесь действуют два различных правила: параметр типа массива приспосабливается к параметру типа указателя во время компиляции, а выражение типа массива преобразуется (распадается) к значению указателя, условно во время выполнения. - person Keith Thompson; 12.02.2014
comment
@KeithThompson, пока наблюдаемое поведение таково, это не имеет большого значения. Сущность, вызывающая main, не обязательно написана на C, но если бы это было так, то это могло бы произойти. (прежде чем я продолжу, char *argv[] и char **argv одинаковы (как вы тоже сказали), поэтому я не буду обсуждать первое правило, которое вы упомянули выше). Точнее, скорее всего, здесь задействован char **argv = malloc(...) (если main-вызывающая сущность была написана на C), так что ни массив, ни затухание до указателя вообще не задействованы! - person Shahbaz; 12.02.2014
comment
Немного придирчиво, но в вашем примере строковые литералы не имеют типа const char * в C, поэтому нет необходимости использовать strdup(), чтобы избежать этого, и у вас должен быть нулевой указатель в качестве последнего элемента вашего массива argv. - person Crowman; 12.02.2014
comment
Опять же, наблюдаемое поведение такое же; похоже, что сущность, вызвавшая main, создала массив, и этот массив превратился в указатель. Это объясняет вопрос ОП относительно задействованных массивов. - person Shahbaz; 12.02.2014
comment
Маловероятно, что malloc замешан; free(argv) имеет неопределенное поведение. - person Keith Thompson; 12.02.2014
comment
@PaulGriffiths: строковые литералы не являются const, но вы не можете их изменить; вам явно разрешено изменять строки аргументов. - person Keith Thompson; 12.02.2014
comment
@KeithThompson: С этим не поспоришь, каламбур непреднамеренный. - person Crowman; 12.02.2014
comment
@PaulGriffiths, спасибо, я не знал, что строковые литералы в C на самом деле не относятся к типу const char []. И да, я забыл о NULL. - person Shahbaz; 12.02.2014
comment
@KeithThompson, неопределенное поведение не означает, что библиотекам запрещено это делать. Это означает, что стандарт не обязывает malloc к использованию, но и не запрещает его. На самом деле реализация glibc в execvpe использует alloca или malloc в зависимости от конфигурации. - person Shahbaz; 12.02.2014
comment
+1 за простое и терпеливое обращение к каждой из этих (несколько незначительных) гнид, все от людей со знаниями, достаточно существенными, чтобы найти дыры в объяснении, которое в противном случае, казалось, обращалось к ОП очень ясным и простым способом. - person ryyker; 12.02.2014
comment
@ryyker, спасибо. Трудно найти золотую середину между абсолютно правильным и не супер-ужасным для новичка. Мне лично понравилось, что Кейт объяснил различные синтаксисы для аргументов функции указателя (в своем ответе), что, я думаю, важно знать OP. - person Shahbaz; 13.02.2014

На вопросы, которые вы задаете, действительно лучше всего ответить, просто сказав, что все «по определению». т. е. набор правил, разработанных и согласованных комитетом.

Вот что говорит C11: (см. выделенные разделы)

5.1.2.2.1 Запуск программы
1 Функция, вызываемая при запуске программы, называется main. Реализация не объявляет прототип для этой функции. Он должен быть определен с возвращаемым типом int и без параметров: int main(void) { /* ... */ } или с двумя параметрами (упоминаемыми здесь как argc и argv, хотя могут использоваться любые имена, т.к. они являются локальными для функции, в которой они объявлены): int main(int argc, char argv[]) { / ... */ } или эквивалент;10) или в какой-либо другой реализации- определенным образом.
2 Если они объявлены, параметры основной функции должны подчиняться следующим ограничениям:
— Значение argc должно быть неотрицательным.
— argv[argc] должен быть нулевым указателем.
— Если значение argc больше нуля, члены массива с argv[0] по argv[argc-1] включительно должны содержать указатели на строки, которые перед запуском программы хост-среде присваиваются значения, определенные реализацией.
Намерение состоит в том, чтобы предоставить программе информацию, определенную до запуска программы из другого места хост-среды. Если хост-среда не может предоставлять строки с буквами как в верхнем, так и в нижнем регистре, реализация должна гарантировать, что строки будут получены в нижнем регистре.
— Если значение argc больше нуля, строка, на которую указывает argv [0] представляет имя программы; argv[0][0] должен быть нулевым символом, если имя программы недоступно в хост-среде. Если значение argc больше единицы, строки, на которые указывает argv[1] — argv[argc-1], представляют параметры программы.
— Параметры argc и argv, а также строки, на которые указывает массив argv, должны могут быть изменены программой и сохраняют свои последние сохраненные значения между запуском программы и ее завершением.

person ryyker    schedule 12.02.2014
comment
Это не лучший способ ответить на вопрос, потому что он предполагает, что мы должны просто подчиняться комитетам без причины, и потому что он не объясняет, почему комитеты сделали то, что они сделали. У комитетов, разработавших стандарт C, были причины для того, что они сделали, об этом есть документация, и людям полезно понять, почему язык разработан таким, какой он есть. - person Eric Postpischil; 12.02.2014
comment
Иногда мне было очень полезно понять, что что-то было решено ради стандартизации, возможно, не имеющего большого значения. Вот так - иди дальше. - person spemble; 02.06.2021

Он не определяется как обычный массив, потому что в C размер элементов массива должен быть известен во время компиляции. Размер char * известен, размер (длина) ваших аргументов — нет.

argv[0] содержит имя вызываемого процесса, потому что его можно вызывать под любым произвольным именем. например exec семейство вызовов может указать, что ему нужно, и вам разрешено вызывать программу через символическую ссылку. argv[0] позволяет программе предлагать различные функции в зависимости от имени вызова.

person Sergey L.    schedule 12.02.2014