Перенос[][] массива указателей

#c #arrays #pointers #casting

#c #массивы #указатели #Кастинг

Вопрос:

У меня возникли проблемы с API-интерфейсом поставщика для извлечения данных с устройства. Рабочая версия вызова, предложенная в документации поставщика, выглядит примерно следующим образом:

 int dataArray[15][20];
getData(15, 20, (int*) dataArray);
 

Сигнатура вызова описывается как:

 void getData(xSize, ySize, int*);
 

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

 int *flatDataArray = (int*) malloc(xSize * ySize * sizeof(int));
 

Мне трудно правильно выполнить вызов, а ошибки segfaults несколько сложны для отладки: (

Я думаю, что мое недопонимание связано с тем, как представлен массив int и что происходит, когда вы приводите его к (int *) . В этом примере это «dataArray» типа int ** или что-то еще? Если да, нужно ли вам создавать массив указателей в flatDataArray для правильной работы вышеупомянутой функции?

Комментарии:

1. Указатели можно использовать для перебора одномерных массивов, но указатели на указатели нельзя использовать для перебора двумерных массивов, которые являются просто массивами массивов. Рекомендуемое чтение: раздел 6 FAQ по comp.lang.c .

2. @KeithThompson Я считаю, что они могут, если указатель знает, что он указывает на массив n элементов типа , а не просто указатель на переменную типа .

3. В чем проблема? функция принимает указатель на удвоение, а не указатель на массив удвоений.

4. @ThoAppelsin: Двумерный массив — это просто массив массивов. Использование указателя на указатель не имеет смысла, потому что нет объектов -указателей, на которые указывал бы такой указатель. (Если вы не создаете их самостоятельно.)

5. Просто передайте его как amp;dataArray[0][0].

Ответ №1:

Первая строка «работает» из-за приведения.

Учитывая:

 int dataArray[15][20];
getData(15, 20, (int*) dataArray);
 

Тип dataArray автономного значения выражения int (*)[20] — указатель на массив из 20 . int Это имеет смысл, потому что в соответствии со стандартом C значение выражения массива является адресом его первого элемента и указателем на указанный тип. Ну, первый «элемент» массива массивов (который и есть dataArray ) имеет тип int[20] , и указатель на тип дает нам int(*)[20] .

Тем не менее, те же правила применяются к первому массиву в этом массиве массивов. Короче говоря, это будет работать:

 int dataArray[15][20];
getData(15, 20, dataArray[0]);
 

Точно так же, как dataArray выражение, приводящее к адресу первого элемента (массива) типа int (*)[20] , первый массив в этом массиве также имеет значение выражения, которое является адресом его первого элемента, an int , и тип, связанный с этим адресом int * , таков: это первый элементпервый массив массива массивов. На фоне линейной памяти базовой системы, в которой это выполняется, все это в конечном итоге разрешается по одному и тому же адресу (где dataArray находится в памяти). Это типы, связанные с этим адресом, в зависимости от того, как он придуман, которые отличаются. Однако, независимо от типов, базовый фон памяти один и тот же: непрерывная последовательность из 300 int . Адрес, возвращаемый всеми следующими, будет одинаковым; отличаются только типы (и отмечены в комментарии)

 amp;dataArray            // int (*)[15][20]
dataArray             // int (*)[20]
dataArray[0]          // int *
amp;dataArray[0][0]      // int *
 

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

В любом случае, выделение, как вы здесь:

 int *flatDataArray = malloc(xSize * ySize * sizeof(int));
 

работает, потому что вы просто создаете xSize by ySize линейный фон. Поскольку это C, также будет работать следующее, используя функцию VLA (массив переменной длины) языка:

 int (*arr2D)[ySize] = malloc(xSize * sizeof(*arr2D));
 

предполагается xSize , что это количество строк и ySize количество столбцов, которые вы ищете (кажется, я ошибаюсь в половине случаев, поэтому я предпочитаю прозвища «row» и «col»). В приведенном выше распределении говорится: «Выделите мне место для xSize нескольких int[ySize] вещей».. Преимущество в коде заключается в том, что вы можете решить это так же, как и 2D-массив массивов (что именно так и есть):

 arr2d[i][j] = value;
 

для любого i in 0..(xsize-1) и j in 0..(ysize-1) , точно так же, как вы бы / могли, как если бы вы объявили это нормально. Это (VLAs) — это то, что C делает хорошо, чего не делает C (но опять же, в C есть множество контейнеров, которые в первую очередь решили бы эту проблему по-другому, так что это не совсем справедливое сравнение).

Желаю удачи.

Ответ №2:

Для 2-мерного массива, объявленного следующим образом:

 double dataArray[15][20];
 

Упрощенная версия будет просто следующей:

 dataArray[0];
 

И если бы вы сохранили этот адрес первого double в указатель (переменную адреса) на double:

 double * flatDataArray = dataArray[0];
 

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

 for (int i = 0; i < 15 * 20; i  ) {
    dataArray[0][i];    // alternatively: flatDataArray[i];
 

Согласно редактированию: измените все это double на int , или любой другой тип, который вы пожелаете.

Скажем, что это было из char s, вот как это будет выглядеть, и что dataArray , *dataArray и **dataArray будет напоминать:

    char0  char1  ...  char14  char15  char16  ...  char29  ...  char299
// >                             dataArray                            <
// >     *dataArray        <  >                         <               
// >   <  >   <
//   ^ the **dataArray
 

dataArray Разыменование может не выполняться больше, максимум в два раза. Взяв адрес **dataArray , вы будете иметь *dataArray на руках. Аналогично для *dataArray . Не для dataArray , и в этом случае вы предпочтете просто amp;dataArray .

Каждый из amp;dataArray dataArray и *dataArray будет иметь одно и то же значение, которое является адресом char0 или массива, который простирается от char0 до char14 , или массива массивов, который простирается от char0-char14 до char285-char299 .

**dataArray будет иметь значение, которое char0 сохраняется.

В чем разница между тремя другими? Ну, они указывают на разные типы, следовательно, их увеличение будет иметь разные интерпретации. Это похоже на то, как при увеличении целочисленного указателя вы продвигаете 4 байта вместо 1.

Если вы увеличите amp;dataArray значение на 1, например amp;dataArray 1 , вы увеличите размер на 300 байт ( 300 * sizeof(char) ). Если вы увеличите dataArray значение на 1, что является адресом *dataArray , вы увеличите размер на 15 байт и так далее.

Если вам случится увеличить значение *dataArray на 1, которое является адресом **dataArray char0 , вы увеличите значение на 1 байт. Теперь, вот что:

  • *dataArray является dataArray[0]
  • увеличение его на 1 равно dataArray[0] 1
  • это разыменованное было бы *(dataArray[0] 1)
  • сокращение для этого dataArray[0][1]
  • допустим, мы должны были увеличить его больше, скажем, в 15 раз в общей сложности
  • dataArray[0][15]

Кажется незаконным, не так ли? Ну, это не так, потому что этот бит памяти все еще находится в выделенной для вас части. На самом деле, вы можете зайти так далеко, как dataArray[0][299] . Это просто означает продвижение на 299 байт и доступ к этому местоположению, и это местоположение полностью ваше.

Безумие, не так ли? Еще более безумно, что я так много писал об этом. Я даже не уверен, отвечает ли это на ваш вопрос… Поэтому я полагаю, что вызов типа getData(15, 20, dataArray[0]); or getData(15, 20, *dataArray); был бы более разумным, хотя я сомневаюсь, что приведение к типу там завершится неудачей.

Я надеюсь, что это, по крайней мере, будет иметь смысл для кого-то.

Комментарии:

1. Большое спасибо за тщательность. Между этим ответом и комментариями, я думаю, у меня есть то, что мне нужно, чтобы собрать все это воедино. Важным моментом является то, что в зависимости от того, как поставщик реализовал функцию, переданный указатель может иметь одну из двух семантик. Я считаю, что, поскольку размер неизвестен API во время компиляции, должно быть какое-то руководство по арифметике указателей.

Ответ №3:

Этот код в порядке:

 int dataArray[15][20];
getData(15, 20, (int*) dataArray);
 

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

Было бы неопределенным поведением для записи getData(15, 20, dataArray[0]); , потому что в этом случае вы используете указатель только на массив из 20 целых чисел, который был первым элементом dataArray, поэтому вам не разрешено переполнять эти 20 целых чисел. (Ссылка: C99 6.5.6#8)

Я заинтересован в том, чтобы эта функция копировала данные в непрерывный массив двойников с динамическим размером

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

 int array1[xSize][ySize];
getData( (int *)int_array );

double array2[xSize][ySize];
for ( size_t ii = 0; ii < xSize;   ii )
    for ( size_t jj = 0; jj < ySize;   jj )
         array2[xSize][ySize] = array1[xSize][ySize];
 

В последней строке выполняется преобразование значения из int в double .

В C массивы могут иметь такие размеры во время выполнения. (На самом деле в 2011 году это было изменено на необязательную функцию, ранее она была стандартной). Если вы используете компилятор, который больше не поддерживает массив такого типа, вы можете использовать malloc .

Если оба измерения неизвестны во время компиляции, то вам нужно создать одномерный массив, а затем выполнить в нем смещения; например

 int *array1 = malloc(xSize * ySize * sizeof *array1);
getData(array1);

double *array2 = malloc(xSize * ySize * sizeof *array2);
for (size_t ii = 0; ii < xSize * ySize;   ii)
    array2[ii] = array1[ii];

// what was previously array1[4][6]
array2[4 * ySize   6];
 

NB. void getData(xSize, ySize, int*); не является допустимой сигнатурой вызова. Сигнатура вызова будет иметь типы данных вместо имен переменных. Вы можете найти объявление функции в заголовочном файле, который вы включаете для доступа к этому API.