Понимание операций разыменования, адреса и индекса массива в C

У меня argv[] определен как char *. Используя следующие операторы printf:

     printf("%s\n",argv[1]);   // prints out the entire string
     printf("%p\n",&argv[1]);  // & -> gets the address
     printf("%c\n",argv[1][0]);// prints out the first char of second var
     printf("%c\n",*argv[1]);  //  

Вот этого последнего я не понимаю. Что значит напечатать *argv[1]? почему это не то же самое, что *argv[1][0], и почему вы не можете распечатать printf("%s\n",*argv[1]);. Кроме того, почему адрес &*argv[1] отличается от адреса &argv[1]?


person DCR    schedule 15.01.2017    source источник
comment
Вам может помочь чтение char* страницы тегов. Возможно также этот вопрос относительно argc и argv.   -  person einpoklum    schedule 15.01.2017
comment
Проблема не в понимании char*, а в указателях   -  person Mateusz Wojtczak    schedule 15.01.2017
comment
@ user3655463: Видя, как OP хотел разыменовать argv[1][0], я не согласен.   -  person einpoklum    schedule 15.01.2017


Ответы (5)


Операция индекса массива a[i] определяется как *(a + i) - учитывая адрес a, смещение i элементов (не байтов) от этого адреса и разыменование результата. Таким образом, для указателя p *p эквивалентно *(p + 0), что эквивалентно p[0].

Тип argvchar **; при этом верно все следующее:

    Expression         Type            Value
    ----------         ----            -----
          argv         char **         Pointer to a sequence of strings
         *argv         char *          Equivalent to argv[0]
        **argv         char            Equivalent to argv[0][0]
       argv[i]         char *          Pointer to a single string
      *argv[i]         char            Same as argv[i][0]
    argv[i][j]         char            j'th character of i'th string
      &argv[i]         char **         Address of the pointer to the i'th string

Поскольку тип argv[i][j] равен char, *argv[i][j] не является допустимым выражением.

Вот плохая визуализация последовательности argv:

     +---+              +---+                                         +---+
argv |   | ---> argv[0] |   | ---------------------------> argv[0][0] |   |
     +---+              +---+                     +---+               +---+
                argv[1] |   | -------> argv[1][0] |   |    argv[0][1] |   |
                        +---+                     +---+               +---+
                         ...           argv[1][1] |   |                ...
                        +---+                     +---+               +---+
             argv[argc] |   | ---|||               ...   argv[0][n-1] |   |
                        +---+                     +---+               +---+
                                     argv[1][m-1] |   |
                                                  +---+

Это может помочь объяснить результаты различных выражений.

person John Bode    schedule 15.01.2017

char *argv[]

argv – это массив(1) указателей на символы. Так что это обычный массив, просто каждый элемент массива является указателем. argv[0] — указатель, argv[1] и т. д.

argv[0] - первый элемент в массиве. Поскольку каждый элемент в массиве является указателем на символ, значение this также является указателем на символ (как мы уже упоминали выше).

*argv[1] - Здесь argv[1] является вторым элементом в приведенном выше массиве, но argv[1] также является указателем на символ. Применение * просто разыменовывает указатель, и вы получаете первый символ в строке, на который указывает argv[1]. Вы должны использовать %c для его печати, так как это всего лишь символ.

argv[1][0] уже является первым символом второй строки в массиве, поэтому больше нет места для разыменования. Это по сути то же самое, что и предыдущее.


(1) как выделено, строго говоря, что это указатель на указатель, но, возможно, вы можете «думать» об этом как о массиве указателей. В любом случае больше информации об этом здесь: https://stackoverflow.com/a/39096006/3963067

person Giorgi Moniava    schedule 15.01.2017
comment
@HolyBlackCat выглядит лучше? - person Giorgi Moniava; 15.01.2017

Если argv[1] является указателем на char, то *argv[1] разыменовывает этот указатель и дает вам первый символ строки в argv[1], поэтому он совпадает с argv[1][0] и печатается с описателем формата "%c".

argv[1][0] — это сам char, а не указатель, поэтому его нельзя разыменовать.

person ForceBru    schedule 15.01.2017
comment
Кстати, я не совсем уверен, какое правильное написание для dereferencable, поскольку dereferencable выглядит уродливо и подчеркивается как неправильное автозаменой macOS, но dereferensable чувствует себя лучше, хотя также подчеркивается. Кто-нибудь знает, какое написание правильное? - person ForceBru; 15.01.2017

  1. Это не относится к char *.
  2. Вы можете упростить, в чем разница между *ptr и ptr[0].
  3. Нет никакой разницы, потому что ptr[0] — это сахар для *(ptr + 0) или *ptr, потому что + 0 бесполезен.

// printf("%p\n", &argv[1]); is wrong you must cast to (void *)
printf("%p\n", (void *)&argv[1]);

Поскольку спецификатор %p ожидает void *, в обычном случае C автоматически продвигает ваш указатель в void *, но printf() использует список переменных аргументов. По этому поводу есть много правил, я предлагаю вам прочитать doc если хотите. Но char * не будет повышаться до void * и, как я уже сказал, printf(), кроме void *, так что у вас будет неопределенное поведение, если вы не примените его сами.

person Stargateur    schedule 15.01.2017
comment
@GovindParmar Приведение необходимо для обеспечения совместимого типа указателя. p Аргумент должен быть указателем на void. C11dr §7.21.6.1 8 - person chux - Reinstate Monica; 15.01.2017
comment
Я получаю один и тот же адрес независимо от того, использую ли я (void *)&argv[1] или &argv[1] - person DCR; 15.01.2017
comment
@DCR Да, это нормально, я никогда не использую систему, в которой указатель имеет другой размер, но это возможно в C. Например, в некоторых системах есть `sizeof (int *) != sizeof (void *). Это просто примечание, но вы хотите написать правильный и переносимый C, важно знать, что вы должны его привести. - person Stargateur; 15.01.2017

Последняя строка printf("%c\n",*argv[1]); одновременно разыменовывает argv и обращается к индексу массива 1. Другими словами, здесь выполняется argv[1][0], как и в предыдущей строке, поскольку доступ к индексу массива [1] имеет более высокий приоритет, чем оператор разыменования (*).

Однако, если бы вам нужно было заключить выражение в последней строке в скобки, чтобы оператор разыменования обрабатывался первым, вы бы сделали следующее:

printf("%c\n", (*argv)[1]);

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

person Govind Parmar    schedule 15.01.2017