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

Различия при использовании ** в C

Недавно я начал изучать C, и у меня возникла проблема с пониманием синтаксиса указателя, например, когда я пишу следующую строку:

int ** arr = NULL;

Как узнать, если:

  • arr является указателем на указатель целого числа

  • arr - указатель на массив указателей на целые числа

  • arr является указателем на массив указателей на массивы целых чисел

Разве это не все равно с int **?


Другой вопрос по той же проблеме:

Если у меня есть функция, которая получает char ** s в качестве параметра, я хочу ссылаться на нее как на pointer на массив строк, то есть на указатель на массив указателей на массив из chars, но это также указатель на указатель на char?

4b9b3361

Ответ 1

Разве это не все равно с int **?

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

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


Я чувствую, что определенное количество разъяснений в порядке. Как вы могли бы видеть, когда вы консультируетесь с другими очень хорошими ответами, массивы являются определенно не указателями. Они, однако, распадаются на единицы в достаточном контексте, чтобы оправдать долгую ошибку в обучении о них (но я отвлекаюсь).

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

void func(int **p_buff)
{
}

//...

int a = 0, *pa = &a;
func(&pa);

//...

int a[3][10];
int *a_pts[3] = { a[0], a[1], a[2] };
func(a_pts);

//...

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

Предположим func, и каждый фрагмент кода скомпилирован в отдельной единицы перевода. Каждый пример (запрет на любые опечатки мной) действителен C. Массивы будут распадаться на "указатель на указатель" при передаче в качестве аргументов. Как определить func, чтобы узнать, что именно оно было передано из типа его параметра?? Ответ в том, что он не может. Статический тип p_buff равен int**, но он по-прежнему позволяет func косвенно обращаться к (частям) объектов с значительно более эффективными типами.

Ответ 2

Объявление int **arr говорит: "объявлять arr как указатель на указатель на целое число". Он (если действителен) указывает на один указатель, который указывает (если он действителен) на один целочисленный объект. Поскольку можно использовать арифметику указателя с любым уровнем косвенности (т.е. *arr совпадает с arr[0], а **arr совпадает с arr[0][0]), объект может использоваться для доступа к любому из 3 из ваш вопрос (то есть, во-вторых, получить доступ к массиву указателей для целых чисел, а для третьего - получить доступ к массиву указателей на первые элементы целых массивов), при условии, что указатели указывают на первые элементы массивов...


Тем не менее, arr по-прежнему объявляется как указатель на один указатель на один целочисленный объект. Также можно объявить указатель на массив определенных измерений. Здесь a объявлен как указатель на 10-элементный массив указателей на массивы из 10 целых чисел:

cdecl> declare a as pointer to array 10 of pointer to array 10 of int;
int (*(*a)[10])[10]

На практике указатели на массивы наиболее часто используются для передачи в многомерных массивах постоянных измерений в функции и для передачи в массивах переменной длины. Синтаксис объявления переменной как указателя на массив редко встречается, так как всякий раз, когда они передаются в функцию, несколько проще использовать параметры типа "массив undefined size", поэтому вместо объявления

void func(int (*a)[10]);

можно было использовать

void func(int a[][10])

передать в многомерный массив массивов из 10 целых чисел. Альтернативно, typedef может использоваться для уменьшения головной боли.

Ответ 3

Как узнать, если:

  • arr является указателем на указатель целого числа

Он всегда является указателем на целое число.

  • arr - указатель на массив указателей на целые числа
  • arr - указатель на массив указателей на массивы целых чисел

Это никогда не может быть так. Указатель на массив указателей на целые числа будет объявлен следующим образом:

int* (*arr)[n]

Звучит так, как будто вас обманули, чтобы использовать int** бедные учителя/книги/учебники. Это почти всегда неверная практика, как описано здесь и здесь и ( с подробным объяснением указателей на массивы) здесь.

ИЗМЕНИТЬ

Наконец, собрался написать подробное сообщение, объясняющее, какие массивы есть, какие выглядят таблицы, почему последние плохи и что вы должны использовать вместо этого: Правильное распределение многомерных массивы.

Ответ 4

Имея исключительно объявление переменной, вы не можете различать три случая. Можно еще обсуждать, нельзя ли использовать нечто вроде int *x[10], чтобы выразить массив из 10 указателей на ints или что-то еще; но int **x может - из-за арифметики указателя, использоваться тремя различными способами, каждый из которых предполагает разное расположение памяти с (хорошим) шансом сделать неправильное предположение.

Рассмотрим следующий пример, где int ** используется тремя различными способами, т.е. p2p2i_v1 как указатель на указатель на (одиночный) int, p2p2i_v2 как указатель на массив указателей на int, и p2p2i_v3 в качестве указателя на указатель на массив из int. Обратите внимание, что вы не можете различать эти три значения только по типу, который int** для всех трех. Но с разными инициализациями доступ к каждому из них не по назначению дает что-то непредсказуемое, за исключением доступа к самым первым элементам:

int i1=1,i2=2,i3=3,i4=4;

int *p2i = &i1;
int **p2p2i_v1 = &p2i;  // pointer to a pointer to a single int

int *arrayOfp2i[4] = { &i1, &i2, &i3, &i4 };
int **p2p2i_v2 = arrayOfp2i; // pointer to an array of pointers to int

int arrayOfI[4] = { 5,6,7,8 };
int *p2arrayOfi = arrayOfI;
int **p2p2i_v3 = &p2arrayOfi; // pointer to a pointer to an array of ints

// assuming a pointer to a pointer to a single int:
int derefi1_v1 = *p2p2i_v1[0];  // correct; yields 1
int derefi1_v2 = *p2p2i_v2[0];  // correct; yields 1
int derefi1_v3 = *p2p2i_v3[0];  // correct; yields 5

// assuming a pointer to an array of pointers to int's
int derefi1_v1_at1 = *p2p2i_v1[1];  // incorrect, yields ? or seg fault
int derefi1_v2_at1 = *p2p2i_v2[1]; // correct; yields 2
int derefi1_v3_at1 = *p2p2i_v3[1]; // incorrect, yields ? or seg fault


// assuming a pointer to an array of pointers to an array of int's
int derefarray_at1_v1 = (*p2p2i_v1)[1]; // incorrect; yields ? or seg fault;
int derefarray_at1_v2 = (*p2p2i_v2)[1]; // incorrect; yields ? or seg fault;
int derefarray_at1_v3 = (*p2p2i_v3)[1]; // correct; yields 6;

Ответ 5

Как узнать, если:

arr является указателем на указатель целого числа

arr - указатель на массив указателей на целые числа

arr является указателем на массив указателей на массивы целых чисел

Вы не можете. Это может быть любой из них. То, что в конечном итоге зависит от того, как вы его размещаете/используете,

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

Ответ 6

Указатели не сохраняют информацию о том, указывают ли они на один объект или объект, который является элементом массива. Более того, для указателя арифметические отдельные объекты рассматриваются как массивы, состоящие из одного элемента.

Рассмотрим эти объявления

int a;
int a1[1];
int a2[10];

int *p;

p = &a;
//...
p = a1;
//...
p = a2;

В этом примере указатель p имеет дело с адресами. Он не знает, указывает ли адрес, который он хранит, на один объект, например a, или на первый элемент массива a1, который имеет только один элемент или первый элемент массива a2, который имеет десять элементов.

Ответ 7

Тип

int ** arr;

имеет только одну действительную интерпретацию. Это:

arr is a pointer to a pointer to an integer

Если у вас больше информации, чем декларация выше, это все, что вы можете знать об этом, то есть, если arr, вероятно, инициализирован, он указывает на другой указатель, который, если возможно, инициализирован, указывает на целое число.

Предполагая правильную инициализацию, единственным гарантированным действительным способом ее использования является:

**arr = 42;
int a = **arr;

Однако C позволяет использовать его несколькими способами.

• arr может использоваться как указатель на указатель на целое число (т.е. основной случай)

int a = **arr;

• arr может использоваться как указатель на указатель на массив целых чисел

int a = (*arr)[4];

• arr может использоваться как указатель на массив указателей на целые числа

int a = *(arr[4]);

• arr может использоваться как указатель на массив указателей на массивы целых чисел

int a = arr[4][4];

В последних трех случаях это может выглядеть так, как будто у вас есть массив. Однако тип не массив. Тип всегда просто a pointer to a pointer to an integer - разыменование является арифметикой указателя. Это не похоже на 2D-массив.

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

Обновление

Для обновленной части вопроса:

Если у вас есть:

void foo(char** x) { .... };

единственное, что вы точно знаете, это то, что **x даст char и *x даст вам указатель char (в обоих случаях предполагается правильная инициализация x).

Если вы хотите использовать x другим способом, например. x[2], чтобы получить третий указатель char, он требует, чтобы вызывающий объект инициализировал x, чтобы он указывал на область памяти, которая имеет по меньшей мере 3 последовательных указателя char. Это можно охарактеризовать как договор для вызова foo.

Ответ 8

Синтаксис

C является логическим. В качестве звездочки перед идентификатором в объявлении подразумевается указатель на тип переменной, две звездочки означают указатель на указатель на тип переменной.

В этом случае arr есть a pointer to a pointer to integer.

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

Можно также создать двумерный массив, используя это, например

 int **arr=(int**)malloc(row*(sizeof(int*)));  
 for(i=0;i<row;i++) {
 *(arr+i)=(int*)malloc(sizeof(int)*col); //You can use this also. Meaning of both is same. //
  arr[i]=(int*)malloc(sizeof(int)*col); }

Ответ 9

При использовании указателей есть один трюк, прочитайте его с правой стороны в левую сторону:

int** arr = NULL;

Что вы получаете: arr, *, *, int, поэтому array является указателем на указатель на целое число.

И int **arr; совпадает с int** arr;.

Ответ 10

int ** arr = NULL;

Он сообщает компилятору arr is a double pointer of an integer и присваивает значение NULL.

Ответ 11

Здесь уже есть хорошие ответы, но я хочу упомянуть мой сайт "goto" для сложных объявлений: http://cdecl.org/

Посетите сайт, вставьте свое объявление и переведите его на английский.

В int ** arr; говорится declare arr as pointer to pointer to int.

На сайте также показаны примеры. Проверьте себя на них, затем наведите курсор, чтобы увидеть ответ.

(double (^)(int , long long ))foo

лить foo в блок (int, long long), возвращающий double

int (*(*foo)(void ))[3]

объявить foo как указатель на функцию (void), возвращающую указатель на массив 3 из int

Он также преобразует английский в C-объявления, которые являются prety аккуратными - если вы правильно поняли описание.