#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.