Обозначение **argv в основной функции

Возможный дубликат:
argc и argv в main

Мне трудно понять нотацию, используемую для общего объявления основной функции, то есть int main(int argc, char *argv[]). Я понимаю, что на самом деле в функцию main передается указатель на указатель на char, но я нахожу нотацию сложной. Например:

Почему **argv указывает на первый символ, а не на всю строку? Аналогично, почему *argv[0] указывает на то же самое, что и в предыдущем примере.

Почему *argv указывает на всю первую строку, а не на первую char, как в предыдущем примере?

Это немного не связано, но почему *argv + 1 указывает на строку «минус первый символ» вместо указания на следующую строку в массиве?


person hcaulfield57    schedule 13.11.2012    source источник
comment
argv как массив строк: char* argv[] arv[0] — первая строка, argv[0][0] — первый байт первой строки и т. д.   -  person Alex F    schedule 13.11.2012
comment
В C нет строк, только массивы символов. Возможно, вы не понимаете, что означает указание на всю строку.   -  person Kerrek SB    schedule 13.11.2012
comment
Вам нужно понять две несвязанные части базового синтаксиса C: 1) в аргументах функции T[] совпадает с T*. 2) Для указателя p p[i] идентично *(p + i), а также &*p идентично p. Используйте это, чтобы подумать над своим вопросом.   -  person Kerrek SB    schedule 13.11.2012
comment
Похоже, у вас возникли проблемы с пониманием указателей и массивов. Взгляните на эти вопросы и ответы чтобы увидеть, поможет ли это прояснить это, по крайней мере.   -  person Mike    schedule 13.11.2012
comment
Я думаю, что большая часть того, почему меня сбивают с толку, - это непонимание порядка операций.   -  person hcaulfield57    schedule 14.11.2012


Ответы (4)


Рассмотрим программу с argc == 3.

   argv
     |
     v
+---------+         +----------------+
| argv[0] |-------->| program name\0 |
+---------+         +-------------+--+
| argv[1] |-------->| argument1\0 |
+---------+         +-------------+
| argv[2] |-------->| argument2\0 |
+---------+         +-------------+
|    0    |
+---------+

Переменная argv указывает на начало массива указателей. argv[0] — первый указатель. Он указывает на имя программы (или, если система не может определить имя программы, то строка для argv[0] будет пустой строкой; argv[0][0] == '\0'). argv[1] указывает на первый аргумент, argv[2] указывает на второй аргумент и argv[3] == 0 (эквивалентно argv[argc] == 0).

Другая деталь, которую вам, конечно же, нужно знать, это array[i] == *(array + i) для любого массива.

Вы спрашиваете конкретно:

  • Почему **argv указывает на первый символ, а не на всю строку?

*argv эквивалентно *(argv + 0) и, следовательно, argv[0]. Это char *. Когда вы разыменовываете char *, вы получаете «первый» символ в строке. Таким образом, **argv эквивалентно *(argv[0]), *(argv[0] + 0) или argv[0][0].

(Можно обоснованно утверждать, что **argv — это символ, а не указатель, поэтому он не «указывает на первый символ». Это просто другое имя для 'p' из "program name\0".)

  • Аналогично, почему *argv[0] указывает на то же самое, что и в предыдущем примере.

Как отмечалось ранее, argv[0] — это указатель на строку; поэтому *argv[0] должен быть первым символом в строке.

  • Почему *argv указывает на всю первую строку, а не на первый символ, как в предыдущем примере?

Это вопрос условности. *argv указывает на первый символ первой строки. Если вы интерпретируете его как указатель на строку, он указывает на «всю строку» точно так же, как char *pqr = "Hello world\n"; указывает на «всю строку». Если вы интерпретируете его как указатель на один символ, он указывает на первый символ строки. Думайте об этом как о корпускулярно-волновом дуализме, только здесь это двойственность символов и строк.

  • Почему *argv + 1 указывает на строку «минус первый символ» вместо указания на следующую строку в массиве?

*argv + 1 это (*argv) + 1. Как уже говорилось, *argv указывает на первый символ первой строки. Если вы добавите 1 к указателю, он будет указывать на следующий элемент; поскольку *argv указывает на символ, *argv+1 указывает на следующий символ.

*(argv + 1) указывает на (первый символ) следующей строки.

person Jonathan Leffler    schedule 13.11.2012
comment
Это очень полезно, спасибо за ответ. Вы очень помогли мне с пониманием, по крайней мере, я так думаю. Итак, причина, по которой вся строка распечатывается, скажем, в printf с *argv, заключается в том, что printf принимает char *, поэтому печатает всю строку? - person hcaulfield57; 14.11.2012
comment
да. Если вы используете строку формата %s, то printf("%s\n", *argv); напечатает всю строку. Если вы используете printf("%p\n", (void *)*argv);, вы получите только адрес. (Я должен извиниться за то, что я не уверен, что «первый» используется последовательно; обычно это означает «индекс 0».) - person Jonathan Leffler; 14.11.2012
comment
Хорошо, еще раз спасибо за усилия, приложенные к вашему ответу! - person hcaulfield57; 14.11.2012

Все сводится к арифметике указателей.

*argv[0] = *(*(argv + 0)) = **argv

Так как [] имеет более высокий приоритет, чем унарный *.

С другой стороны, *argv дает первую ячейку массива, массив, содержащий указатели. На что указывает этот указатель? Почему массив символов, строка, конечно.

*argv + 1 дает то, что дает, потому что + имеет более низкий приоритет, чем унарный *, поэтому сначала мы получаем указатель на строку, а затем добавляем к ней 1, таким образом получая указатель на второй символ в строке.

person StoryTeller - Unslander Monica    schedule 13.11.2012
comment
Спасибо, это полезно! - person hcaulfield57; 14.11.2012

I understand that what is actually passed to the main function is a pointer to a pointer to char

Нет, передается массив указателей на символы (массив строк символов). Думайте об этом так, если я даю это в командной строке:

>> ./program hello 456

Моя основная программа получит:

argc == 3

argv[0] == program (the name of the program as a string)
argv[1] == hello   (the first parameter as a string)
argv[2] == 456     (the second parameter as a string)

Why does **argv point to the first char and not the whole string?

char *argv[]  //an array of character pointers
*argv         // an array decays to a pointer, so this is functionally equivalent to
              // argv[0]
**argv        // Now the argv[0] decays to a pointer and this is functionally
              // equivalent to (argv[0])[0]

Likewise, why does *argv[0] point to the same thing as the previous example.

См. выше.

Why does *argv point to the whole first string, instead of the first char like the previous example?

См. выше.

person Mike    schedule 13.11.2012
comment
Мое практическое правило переполнения стека № 3: если вопрос начинается с «Я понимаю», они этого не делают. - person Kerrek SB; 13.11.2012
comment
@KerrekSB - это очень мудрое правило. :) - person Mike; 13.11.2012
comment
Последний пример верен, потому что строки на самом деле являются массивами? - person hcaulfield57; 14.11.2012
comment
@hcaulfield57 - Точно. argv — это массив строк. *argv — первая из этих строк. **argv — это первый символ первой строки. Строка, являющаяся массивом символов в C. - person Mike; 14.11.2012

Это все потому, что массив также является указателем на первый элемент массива в c. **argv дважды разыменовывает наш указатель на указатель на char, давая нам char. *argv[0] в основном говорит «разыменовывать этот адрес и возвращать первый элемент в массиве, описанном адресом, который мы только что получили в результате разыменования», что оказывается одним и тем же. *argv разыменовывается только один раз, поэтому у нас все еще есть указатель на char или массив char. *argv + 1 разыменовывает один раз, давая нам первую строку символов, а затем добавляет 1 к адресу, давая нам адрес второго элемента. Поскольку указатели также являются массивами, мы можем сказать, что это массив *argv минус первый элемент.

person Zistack    schedule 13.11.2012
comment
Массив не является указателем. Массивы — это массивы, а указатели — это указатели. - person Kerrek SB; 13.11.2012
comment
Пожалуйста, пересмотрите. Массивы и указатели разные. - person Mike; 13.11.2012
comment
Позволю себе не согласиться. Если вы объявите массив следующим образом: int a[];, это будет то же самое, что сказать: int* a;. По сути, массив — это просто указатель на набор данных, расположенных последовательно в памяти. - person Zistack; 13.11.2012
comment
Как вы понимаете? int a[]; не будет компилироваться, так как нет размера массива. Если у вас есть int c = 3;, вы можете присвоить его адрес указателю, например int *b = &c;. Если вы попробуете это с массивом int a[1]; a = &c;, вы получите error: incompatible types, потому что вы не можете назначить массиву любой адрес, который хотите, как указатель. Потому что это не указатель. Массивы не указывают. - person Mike; 14.11.2012
comment
Затем попробуйте скомпилировать это: int a[3] = {0,1,2}; int b = *a; int c = *(a+2); int* d = a; int e = d[1]; Вы обнаружите, что компилируется просто отлично. Я понимаю, что вы не можете объявить массив без размера, но если вы объявите его с размером, вы можете рассматривать его как указатель во всех смыслах, кроме присваивания. Это связано с тем, что массив фиксируется в ячейке памяти, и, таким образом, массив фактически имеет тип const int *. Однако вы можете присвоить случайный указатель массиву и манипулировать массивом с помощью указателя так же, как указатель является массивом, потому что по сути это одно и то же. - person Zistack; 14.11.2012