Новый массив, полученный из другого массива указателей на строки, только в другом порядке (в C)

Мне нужна небольшая помощь с массивами и указателями, если это возможно...

Я определил некоторые константные строки:

static const Int8U item1[] = {0x4D, 0x61, 0x72, 0x74, 0x65, 0x6C, 0x20, 0x4D, 0x43, 0x50, 0x37, 0x38, 0x31, 0x30, 0x00};
static const Int8U item2[] = {0x4D, 0x61, 0x74, 0x65, 0x6C, 0x20, 0x4D, 0x43, 0x50, 0x37, 0x38, 0x31, 0x30, 0x00};
static const Int8U item3[] = {0x4D, 0x61, 0x65, 0x6C, 0x20, 0x4D, 0x43, 0x50, 0x37, 0x38, 0x31, 0x30, 0x00};

Я ссылаюсь на эти строки в массиве указателей:

const Int8U* original_sequence[] =
{
    item1,
    item2,
    item3
}

#define SEQUENCE_ELEMENT_1 (0)
#define SEQUENCE_ELEMENT_2 (1)
#define SEQUENCE_ELEMENT_3 (2)

Я могу сослаться на любую из строк с помощью чего-то похожего на:

original_sequence[SEQUENCE_ELEMENT1]

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

const CHAR *new_sequence[3] =
{
    original_sequence[SEQUENCE_ELEMENT_2],
    original_sequence[SEQUENCE_ELEMENT_1],
    original_sequence[SEQUENCE_ELEMENT_3]
};

Однако я получаю сообщение об ошибке «выражение должно иметь постоянное значение». Обратите внимание, что я не могу напрямую ссылаться на «item1» и т. д., потому что #define являются ключевыми в идентификации.

Я также пробовал варианты этого, например:

const CHAR **new_sequence[3] =
{
    (char**)original_sequence[SEQUENCE_ELEMENT_2].
    (char**)original_sequence[SEQUENCE_ELEMENT_1].
    (char**)original_sequence[SEQUENCE_ELEMENT_3]
};

Ничто меня никуда не ведет. Я не могу понять, путаюсь ли я с указателями или нарушаю ограничения компилятора? То, что заставляет меня думать, что это не ограничение, заключается в том, что если бы я должен был ссылаться на строки «item1» непосредственно в этом новом массиве, я не думаю, что возникла бы проблема.

Спасибо,

Роб


person Rob    schedule 02.10.2020    source источник
comment
У вас почему-то . вместо , в нескольких местах. array_one и array1 должны быть одним и тем же?   -  person aschepler    schedule 03.10.2020
comment
Спасибо за ваш ответ, теперь я переименовал некоторые элементы, чтобы сделать их более понятными.   -  person Rob    schedule 03.10.2020


Ответы (3)


К сожалению, то, что вы пытаетесь сделать, не разрешено на стандартном языке C.

Черновик C17 объясняет, что вы не можете использовать значение объекта в постоянном выражении адреса:

6.6 Постоянные выражения

...

  1. Адресная константа — это нулевой указатель, указатель на lvalue, обозначающий объект со статической продолжительностью хранения, или указатель на указатель функции; он должен быть создан явно с использованием унарного оператора & или целочисленной константы, приведенной к типу указателя, или неявно с использованием выражения типа массива или функции. Операции индекса массива [] и доступа к членам . и ->, адресные & и косвенные * унарные операторы, а также приведения указателей могут использоваться при создании адресной константы, но значение доступ к объекту с использованием этих операторов невозможен.

Ваш вариант почти работает, но нужно добавить адрес оператора:

const CHAR **new_sequence[3] =
{
    (char**)&original_sequence[SEQUENCE_ELEMENT_2],
    (char**)&original_sequence[SEQUENCE_ELEMENT_1],
    (char**)&original_sequence[SEQUENCE_ELEMENT_3]
};

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

*new_sequence[index] 

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

uint8_t new_sequence[] = {
    SEQUENCE_ELEMENT_2,
    SEQUENCE_ELEMENT_1,
    SEQUENCE_ELEMENT_3
};

char * get_new_sequence_text(int index) {
    return (char*)original_sequence[new_sequence[index]];
}
person user694733    schedule 05.10.2020

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

При инициализации объекта с статической или локальной длительностью хранения каждое выражение в инициализаторе должно быть постоянным выражением или строковым литералом.

Таким образом, единственный способ инициализировать его - использовать постоянные значения, например.

const Int8U *new_sequence[3] =
{
    item2,
    item1,
    item3
};

или сделать это внутри функции

void func()
{
    const Int8U *new_sequence[3] =
    {
        original_sequence[SEQUENCE_ELEMENT_2],
        original_sequence[SEQUENCE_ELEMENT_1],
        original_sequence[SEQUENCE_ELEMENT_3]
    };
}

или сделать это динамически в коде

new_sequence[0] = original_sequence[SEQUENCE_ELEMENT_2];
new_sequence[1] = original_sequence[SEQUENCE_ELEMENT_1];
new_sequence[2] = original_sequence[SEQUENCE_ELEMENT_3];

или есть какая-то другая непрямая схема

int new_sequence_index[] =
{
    SEQUENCE_ELEMENT_2,
    SEQUENCE_ELEMENT_1,
    SEQUENCE_ELEMENT_3
};

и делать доступы через original_sequence[new_sequence_index[0]] и т.д.

person Olaf Dietsche    schedule 05.10.2020

Практически есть две проблемы:

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

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

Например, вы можете сделать это:

#define ITEM1 0x4D, 0x61, 0x72, 0x74, 0x65, 0x6C, 0x20, 0x4D, 0x43, 0x50, 0x37, 0x38, 0x31, 0x30, 0x00
#define ITEM2 0x4D, 0x61, 0x74, 0x65, 0x6C, 0x20, 0x4D, 0x43, 0x50, 0x37, 0x38, 0x31, 0x30, 0x00
#define ITEM3 0x4D, 0x61, 0x65, 0x6C, 0x20, 0x4D, 0x43, 0x50, 0x37, 0x38, 0x31, 0x30, 0x00

static const uint8_t* const data[3] =
{
  (const uint8_t[]) { ITEM1 },
  (const uint8_t[]) { ITEM2 },
  (const uint8_t[]) { ITEM3 },
};

(Я избавился от неприглядных самодельных типов в пользу стандартного C stdint.h, как и все остальные.)

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

enum
{
  SEQUENCE_ELEMENT_1,
  SEQUENCE_ELEMENT_2,
  SEQUENCE_ELEMENT_3,
};

static const size_t original_sequence[3] =
{
  SEQUENCE_ELEMENT_1,
  SEQUENCE_ELEMENT_2,
  SEQUENCE_ELEMENT_3,
};

static const size_t new_sequence[3] =
{
  SEQUENCE_ELEMENT_2,
  SEQUENCE_ELEMENT_1,
  SEQUENCE_ELEMENT_3,
};

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

for(size_t i=0; i<3; i++)
{
  for(size_t j=0; i<something; j++)
  {
    uint8_t a = data[ original_sequence[i] ][j];
    ...
  }
}

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

person Lundin    schedule 05.10.2020