Подтвердить что ты не робот

Что делает ** на языке C

Я новичок в C с хорошим фоном в java, и я пытаюсь понять указатели и массивы.

Я знаю, что индекс operator[] является частью определения массива, поэтому:

int numbers[] = {1,3,4,5};

создаст целочисленный массив, который будет представлен в памяти как 16 байт, 4 лота 4 байта:

numbers[0] = 1, address 0061FF1C
numbers[1] = 3, address 0061FF20
numbers[2] = 4, address 0061FF24
numbers[3] = 5, address 0061FF28

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

int *pNumbers = &numbers[0];

который будет выглядеть примерно так:

указатель на цифры

И я предполагаю, что это будет размером 4 байта?

Однако ** я читаю как "указатель на указатель", который не имеет для меня никакого смысла, зачем кому-то нужен указатель на указатель, конечно, если a- > b- > c, тогда a- > c будет достаточно? Я знаю, что у меня что-то не хватает, и оно должно иметь какое-то отношение к массивам, поскольку argv может иметь тип char[ ] или char **, как показано ниже:

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

Итак:

  • что это (**)?
  • какое использование у него есть?
  • как он представлен в памяти?
4b9b3361

Ответ 1

В аргументах C передаются значения. Например, если в главном

есть целочисленное значение varaible,
int main( void )
{
    int x = 10;
    //...

и следующей функции

void f( int x )
{
    x = 20;
    printf( "x = %d\n", x );
} 

то, если вы вызываете функцию в основном, как это,

f( x );

то параметр получает значение переменной x в основном. Однако сам параметр занимает в разной степени память, чем аргумент. Поэтому любые изменения параметра в функции не влияют на исходную переменную в основном, потому что эти изменения происходят в разной степени памяти.

Итак, как изменить varible в main в функции?

Вам нужно передать ссылку на переменную с помощью указателей.

В этом случае объявление функции будет выглядеть как

void f( int *px );

и определение функции будет

void f( int *px )
{
    *px = 20;
    printf( "*px = %d\n", *px );
} 

В этом случае изменяется размер памяти, занимаемый исходной переменной x, потому что внутри функции мы получаем доступ к этой мере с помощью указателя

    *px = 20;

Естественно, эту функцию нужно вызывать в основном, например,

f( &x );

Учтите, что сам параметр, являющийся указателем px, обычно является локальной переменной функции. То есть функция создает эту переменную и инициализирует ее с адресом переменной x.

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

int main( void )
{
   int *px = malloc( sizeof( int ) );
   //..

И функция, определенная как

void f( int *px )
{
    px = malloc( sizeof( int ) );

    printf( "px = %p\n", px );
}

В качестве параметра px назначается локальная переменная, любое значение не влияет на исходный указатель. Функция меняет разную степень памяти, чем степень, занимаемая исходным указателем px в основном.

Как изменить исходный указатель в функции? Просто передайте его по ссылке!

Например

f( &px );
//...

void f( int **px )
{
    *px = malloc( sizeof( int ) );

    printf( "*px = %p\n", *px );
}

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

Ответ 2

Q: что это (**)?

A: Да, именно это. Указатель на  указатель.

Q: какое использование имеет?

A: Он имеет множество применений. Особенно в представлении 2-мерных данных (изображений и т.д.). В случае вашего примера char** argv можно рассматривать как массив массива char s. В этом случае каждый char* указывает на начало строки. Вы могли бы прямо объявить эти данные явно так.

char* myStrings[] = {
    "Hello",
    "World"
};

char** argv = myStrings;

// argv[0] -> "Hello"
// argv[1] -> "World"

При обращении к указателю, подобному массиву, номер, который вы индексируете, и размер самого элемента используются для смещения по адресу следующего элемента в массиве. Вы также можете получить доступ ко всем своим номерам, и на самом деле это в основном то, что делает C. Имейте в виду, что компилятор знает, сколько байтов использует тип int во время компиляции. Поэтому он знает, насколько велики каждый шаг для следующего элемента.

*(numbers + 0) = 1, address 0x0061FF1C
*(numbers + 1) = 3, address 0x0061FF20
*(numbers + 2) = 4, address 0x0061FF24
*(numbers + 3) = 5, address 0x0061FF28

Оператор * называется оператором разыменования. Он используется для извлечения значения из памяти, на которое указывает указатель. numbers - это буквально просто указатель на первый элемент в вашем массиве.

В случае моего примера myStrings может выглядеть примерно так, если предположить, что указатель/адрес равен 4 байтам, то есть мы находимся на 32-битной машине.

myStrings = 0x0061FF14

// these are just 4 byte addresses
(myStrings + 0) -> 0x0061FF14 // 0 bytes from beginning of myStrings
(myStrings + 1) -> 0x0061FF18 // 4 bytes from beginning of myStrings

myStrings[0] -> 0x0061FF1C // de-references myStrings @ 0 returning the address that points to the beginning of 'Hello'
myStrings[1] -> 0x0061FF21 // de-references myStrings @ 1 returning the address that points to the beginning of 'World'

// The address of each letter is 1 char, or 1 byte apart
myStrings[0] + 0 -> 0x0061FF1C  which means... *(myStrings[0] + 0) = 'H'
myStrings[0] + 1 -> 0x0061FF1D  which means... *(myStrings[0] + 1) = 'e'
myStrings[0] + 2 -> 0x0061FF1E  which means... *(myStrings[0] + 2) = 'l'
myStrings[0] + 3 -> 0x0061FF1F  which means... *(myStrings[0] + 3) = 'l'
myStrings[0] + 4 -> 0x0061FF20  which means... *(myStrings[0] + 4) = 'o'

Ответ 3

Традиционным способом записи аргумента argv является char *argv[], который дает больше информации о том, что это такое, массив указателей на символы (т.е. массив строк).

Однако при передаче массива в функцию он распадается на указатель, оставляя указатель на указатель на char или char **.


Конечно, двойные звездочки могут также использоваться при разыменовании указателя на указатель, поэтому без добавленного контекста в конце вопроса есть два ответа на вопрос, что ** означает в C, в зависимости от контекста.

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

Индексирование и разыменование массива взаимозаменяемо в большинстве мест, потому что для любого указателя или массива p и index i выражение p[i] эквивалентно *(p + i). И если i - 0, то мы имеем *(p + 0), который можно укоротить до *(p), который совпадает с *p.

Как любопытство, поскольку p[i] эквивалентно *(p + i) и дополнению коммутативное свойство, выражение *(p + i) равно равный *(i + p), что приводит к p[i], равному i[p].


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

Просто, чтобы быть ясным: называться ThreeStarProgrammer обычно не комплимент

И еще одно предупреждение: Массив массивов - это не то же самое, что указатель на указатель (ссылка на старый ответ, который также показывает макет памяти указателя на указатель в качестве замены массива массивов.)

Ответ 4

** в объявлении представляет указатель на указатель. Указатель сам по себе является типом данных, и, как и другие типы данных, он может иметь указатель.

int i = 5, j = 6; k = 7;
int *ip1 = &i, *ip2 = &j; 
int **ipp = &ip1;  

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

Указатель на указатель полезен в случае выделения динамического 2D-массива. Чтобы выделить 2D-массив 10x10 (может быть не смежным)

int **m = malloc(sizeof(int *)*10;  
for(int i = 0; i < 10; i++)
    m[i] = malloc(sizeof(int)*10  

Он также используется, когда вы хотите изменить значение указателя через функцию.

void func (int **p, int n)  
{
    *p = malloc(sizeof(int)*n); // Allocate an array of 10 elements 
}

int main(void)
{
    int *ptr = NULL;
    int n = 10;
    func(&ptr, n);
    if(ptr)
    {
        for(int i = 0; i < n; i++)
        {  
             ptr[i] = ++i;
        }  
    }

    free(ptr);
}

Дополнительная литература: Указатель на указатель.

Ответ 5

Рассмотрим, есть ли таблица указателей - например, таблица строк (поскольку строки в "С" обрабатываются просто как указатели на первый символ строки).

Затем вам понадобится указатель на первый указатель в таблице. Следовательно, "char **".

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

Другое разъяснение здесь. В "C" разыменование с помощью нотации указателя (например, "* ptr" ) против нотации индекса массива (например, ptr [0]) имеет небольшую разницу, отличную от очевидного значения индекса в нотации массива. Единственное время, когда звездочка и скобки действительно имеет значение, - это когда выделение переменной (например, int * x; очень отличается от int x [1]).

Ответ 6

Из вашего примера int * вы скажете

И я предполагаю, что это будет размером 4 байта?

В отличие от Java, C не указывает точные размеры его типов данных. Различные реализации могут и могут использовать разные размеры (но каждая реализация должна быть последовательной). 4-байтовые int являются общими в наши дни, но int могут быть как малые два байта, и ничто изначально не ограничивает их до четырех. Размер указателей еще менее определен, но обычно он зависит от архитектуры оборудования, на которой нацелена реализация C. Наиболее распространенными размерами указателей являются четыре байта (типичные для 32-разрядных архитектур) и восемь байтов (общий для 64-разрядных архитектур).

что это (**)?

В контексте, который вы представляете, он является частью указателя типа char **, который описывает указатель на указатель на char, как вы думали.

какое использование имеет?

Более или менее то же самое, что и указатель на любой другой тип данных. Иногда вам требуется или нужно косвенно получать доступ к значению указателя, так же, как вам может понадобиться или нужно косвенно получать доступ к значению любого другого типа. Кроме того, полезно указывать (первый элемент) массив указателей, то есть как он используется во втором параметре для функции C main().

В этом конкретном случае каждый char * в самом указанном массиве указывает на один из аргументов командной строки программы.

как он представлен в памяти?

C не указывает, но обычно указатели на указатели имеют то же представление, что и указатели на любой другой тип значения. Значение, на которое указывает это, является просто значением указателя.

Ответ 7

** означает указатель на указатель, как вы знаете имя. Я объясню каждый из ваших вопросов:

что это (**)?

Указатель на указатель. Иногда люди называют двойной указатель. Например:

int a = 3;
int* b = &a; // b is pointer. stored address of a
int**b = &b;  // c is pointer to pointer. stored address of b
int***d = &c; // d is pointer to pointer to pointer. stored address of d. You get it. 

как он представлен в памяти?

c в приведенном выше примере является просто нормальной переменной и имеет то же представление, что и другие переменные (указатель, int...). Размер памяти переменной c такой же, как b, и это зависит от платформы. Например, 32-разрядный компьютер, каждый адрес переменной содержит 32 бит, поэтому размер будет 4 байта (8x4 = 32 бит). На 64-битном компьютере каждый переменный адрес будет 64 бит, поэтому размер будет 8 байтов (8x8 = 64 бит).

какое использование имеет?

Существует много способов использования указателя на указатель, зависит от вашей ситуации. Например, вот один пример, который я узнал в своем классе алгоритмов. У вас есть связанный список. Теперь вы хотите написать метод для изменения этого связанного списка, и ваш метод может изменить заголовок связанного списка. (Пример: удалить один элемент со значением равным 5, удалить элемент head, swap,...). Итак, у вас есть два случая:

1. Если вы просто передаете указатель элемента head. Возможно, этот элемент заголовка будет удален, и этот указатель уже недействителен.

2. Если вы передадите указатель указателя элемента head. В случае удаления элемента head вы не сталкиваетесь с проблемой, поскольку указатель указателя все еще существует. Он просто меняет значения другой головки node.

Здесь вы можете ссылаться на приведенный выше пример: указатель на указатель в связанном списке

Другое использование используется в двумерном массиве. C отличается от Java. Двумерный массив в C, на самом деле просто непрерывный блок памяти. Двумерный массив в Java - это блок с несколькими ячейками памяти (зависит от вашей строки матрицы)

Надеюсь, что эта помощь:)

Ответ 8

Прежде всего, помните, что C обрабатывает массивы очень иначе, чем Java. Объявление типа

char foo[10];

выделяет достаточное количество хранилищ для 10 char значений и ничего больше (по модулю любое дополнительное пространство для удовлетворения требований к выравниванию); дополнительное хранилище не выделяется для указателя на первый элемент или любых других метаданных, таких как размер массива или тип класса элемента. Нет объекта foo, кроме самих элементов массива 1. Вместо этого существует правило на языке, которое когда компилятор видит выражение массива, которое не является операндом оператора sizeof или унарного & (или строкового литерала, используемого для инициализации другого массива в объявлении), оно неявно преобразует это выражение из типа "N-element array of T" в "pointer to T", а значение выражения - это адрес первого элемента массива.

Это имеет несколько последствий. Во-первых, когда вы передаете выражение массива в качестве аргумента функции, то, что фактически получает функция, является значением указателя:

char foo[10];
do_something_with( foo );
...
void do_something_with( char *p )
{
  ...
}

Формальный параметр p, соответствующий фактическому параметру foo, является указателем на char, а не массивом char. Чтобы сделать вещи запутанными, C позволяет do_something_with быть объявленным как

void do_something_with( char p[] )

или даже

void do_something_with( char p[10] )

но в случае объявления параметров функции T p[] и T p[N] идентичны T *p, и все три объявляют p как указатель, а не массив 2. Обратите внимание, что это верно только для объявлений параметров параметров.

Вторая причина заключается в том, что индексный оператор [] может использоваться для операндов указателей, а также для операндов массива, таких как

char foo[10];
char *p = foo;
...
p[i] = 'A'; // equivalent to foo[i] = 'A';

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

const char *strs[] = { "foo", "bar", "bletch", "blurga", NULL };

strs представляет собой 5-элементный массив const char * 3; однако, если вы передадите его функции типа

do_something_with( strs );

то, что получает функция, на самом деле является указателем на указатель, а не массивом указателей:

void do_something_with( const char **strs ) { ... }

Указатели на указатели (и более высокие уровни косвенности) также отображаются в следующих ситуациях:

  • Запись в параметр типа указателя: Помните, что C передает все параметры по значению; формальный параметр в определении функции - это другой объект в памяти, чем фактический параметр в вызове функции, поэтому, если вы хотите, чтобы функция обновляла значение фактического параметра, вы должны передать указатель на этот параметр:
    < ш >
    void foo( T *param ) // for any type T
    {
      *param = new_value(); // update the object param *points to*
    }
    
    void bar( void )
    {
      T x;
      foo( &x );   // update the value in x
    }
    
    Предположим теперь, что мы заменим тип T на тип указателя R *, тогда наш фрагмент кода выглядит следующим образом:

    void foo( R **param ) // for any type R *
    {
      ...
      *param = new_value(); // update the object param *points to*
      ...
    } 
    
    void bar( void )
    {
      R *x;
      foo( &x );   // update the value in x
    }
    
    Та же семантика - мы обновляем значение, содержащееся в x. Это просто, что в этом случае x уже имеет тип указателя, поэтому мы должны передать указатель на указатель. Это можно распространить на более высокие уровни направления:

    void foo( Q ****param ) // for any type Q ***
    {
      ...
      *param = new_value(); // update the object param *points to*
      ...
    } 
    
    void bar( void )
    {
      Q ***x;
      foo( &x );   // update the value in x
    }
    
  • Динамически распределенные многомерные массивы. Одной из распространенных методик выделения многомерных массивов в C является выделение массива указателей, и для каждого элемента этого массива выделяется буфер, в котором указатель указывает на:

    T **arr;
    arr = malloc( rows * sizeof *arr );  // arr has type T **, *arr has type T *
    if ( arr )
    {
      for ( size_t i = 0; i < rows; i++ )
      {
        arr[i] = malloc( cols * sizeof *arr[i] ); // arr[i] has type T *
        if ( arr[i] )
        {
          for ( size_t j = 0; j < cols; j++ )
          {
            arr[i][j] = some_initial_value();
          }
        }
      }
    }
    
    Это можно расширить до более высоких уровней косвенности, поэтому у вас есть такие типы, как T *** и T **** и т.д.


1. Это часть того, почему выражения массива не могут быть объектом назначения; там ничего не назначить.
  1. Это выдержка из языка программирования B, из которого был получен C; в B указатель объявляется как auto p[].

  2. Каждый строковый литерал представляет собой массив char, но поскольку мы не используем их для инициализации отдельных массивов char, выражения преобразуются в значения указателя.

Ответ 9

** представляет указатель на указатель. Если вы хотите передать параметр по ссылке, вы должны использовать *, но если вы хотите передать указатель сам по ссылке, вам понадобится указатель на указатель, следовательно **.

Ответ 10

Это указатель на указатель. Если вы спрашиваете, почему вы хотите использовать указатель на указатель, вот аналогичный поток, который отвечает на это множеством хороших способов.

Зачем использовать двойной указатель? или Зачем использовать указатели для указателей?

Ответ 11

Я думаю, что я собираюсь добавить свой собственный ответ здесь, так как все проделали потрясающую работу, но я был действительно смущен тем, что означало указатель на указатель. Причина, по которой я пришел с этим, заключается в том, что у меня сложилось впечатление, что все значения, кроме указателей, были переданы по значению, а указатели были переданы по ссылке. Смотрите следующее:

void f(int *x){
    printf("x: %d\n", *x);
    (*x)++;
}

void main(){
   int x = 5;
   int *px = &x;
   f(px);
   printf("x: %d",x);
}

будет производить:

x: 5
x: 6

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

Мне это показалось неправильным, и это правильно, потому что было бы глупо иметь указатель для манипулирования указателем, когда вы уже можете манипулировать указателем в функции. Дело в C, хотя; это все передается по значению, даже указатели. Позвольте мне объяснить далее, используя некоторые псевдо значения вместо адресов.

//this generates a new pointer to point to the address so lets give the
//new pointer the address 0061FF28, which has the value 0061FF1C.
void f(int 0061FF1C){
    // this prints out the value stored at 0061FF1C which is 5
    printf("x: %d\n", 5);
    // this FIRST gets the value stored at 0061FF1C which is 5
    // then increments it so thus 6 is now stored at 0061FF1C
    (5)++;
}

void main(){
   int x = 5;

   // this is an assumed address for x
   int *px = 0061FF1C;

   /*so far px is a pointer with the address lets say 0061FF24 which holds
    *the value 0061FF1C, when passing px to f we are passing by value...
    *thus 0061FF1C is passed in (NOT THE POINTER BUT THE VALUE IT HOLDS!)
    */

   f(px);

   /*this prints out the value stored at the address of x (0061FF1C) 
    *which is now 6
    */
   printf("x: %d",6);
}

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

Ответ 12

Я бы понял char **argv как char** argv. Теперь char* - это в основном массив char, поэтому (char*)* - это массив массивов char.

В других (свободных) словах argv представляет собой массив строк. В этом конкретном примере: вызов

myExe dummyArg1 dummyArg2

в консоли будет <<26 >

argv[0] = "myExe"
argv[1] = "dummyArg1"
argv[2] = "dummyArg2"

Ответ 13

Например, ** является указателем на указатель. char **argv совпадает с char *argv[], и это то же самое с char argv[][]. Это матрица.

Вы можете объявить матрицу с 4 строками, например, но различное количество столбцов, например JaggedArrays.

Он представлен как матрица.

Здесь у вас есть представление в памяти.

Ответ 14

В самом деле, в C массивах указаны указатели:

char* string = "HelloWorld!";

эквивалентно этому: char string[] = "HelloWorld"; И это: char** argv, как вы сказали, "указатель на указатель".

Он может рассматриваться как массив строк, т.е. несколько строк. Но помните, что строки char указатели!

См.: в Java основной метод похож на основную функцию C. Это что-то вроде этого:

public static void main(String[] args){}

I. - массив строк. Он работает одинаково в C, String[] args становится char** args или char* args[].

Подводя итог: type* name = blablabla; потенциально представляет собой массив типа. И type** name = blabla; потенциально представляет собой массив массивов.