Доступ к массиву с конца в C? - PullRequest
9 голосов
/ 02 мая 2020

Я недавно заметил, что в C есть важное различие между array и &array для следующего объявления:

char array[] = {4, 8, 15, 16, 23, 42};

Первым является указатель на символ , а последний - указатель на массив из 6 символов . Также следует отметить, что надпись a[b] является синтаксисом c сахара для *(a + b). В самом деле, вы могли бы написать 2[array], и он отлично работает в соответствии со стандартом.

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

char last_element = (&array)[1][-1];

&array имеет размер из 6 символов, поэтому (&array)[1]) - указатель на символы, расположенные сразу после массива. Глядя на [-1], я получаю доступ к последнему элементу.

С этим я мог бы, например, поменять весь массив:

void swap(char *a, char *b) { *a ^= *b; *b ^= *a; *a ^= *b; }

int main() {
    char u[] = {1,2,3,4,5,6,7,8,9,10};

    for (int i = 0; i < sizeof(u) / 2; i++)
        swap(&u[i], &(&u)[1][-i - 1]);
}

Имеет ли этот метод для доступа к массиву к концу fl aws?

Ответы [ 3 ]

12 голосов
/ 02 мая 2020

Стандарт C не определяет поведение (&array)[1].

Рассмотрим &array + 1. Это определяется стандартом C по двум причинам:

  • При выполнении арифметики указателя c результат определяется для результатов из первого элемента (с индексом 0) массива в один за последним элементом.
  • При выполнении арифметики указателя c указатель на отдельный объект ведет себя как указатель на массив с одним элементом. В этом случае &array является указателем на один объект (который сам по себе является массивом, но арифметика указателя c предназначена для указателя на массив, а не указателя на элемент).

То есть &array + 1 - это определенная арифметика указателя c, которая указывает сразу за концом array.

Однако, по определению оператора индекса, (&array)[1] *(&array + 1). Хотя &array + 1 определен, применение * к нему не имеет. C 2018 6.5.6 8 явно говорит нам о результате арифметики указателя c: «Если результат указывает на один элемент после последнего элемента массива, он не должен использоваться в качестве операнда унарного * оператор, который оценивается. ”

Из-за того, как разработано большинство компиляторов, код в вопросе может перемещать данные по вашему желанию. Тем не менее, это не то поведение, на которое вы должны положиться. Вы можете получить хороший указатель сразу за последним элементом массива с помощью char *End = array + sizeof array / sizeof *array;. Затем вы можете использовать End[-1] для ссылки на последний элемент, End[-2] для ссылки на предпоследний элемент и т. Д.

1 голос
/ 03 мая 2020

Хотя в стандарте указано, что arrayLvalue [i] означает (*((arrayLvalue)+(i))), который будет обработан путем взятия адреса первого элемента arrayLvalue, g cc иногда обрабатывает [] при применении к массиву тип value или lvalue, как оператор, который ведет себя в индексированной версии синтаксиса .member, получая значение или lvalue, которое компилятор будет рассматривать как часть типа массива. Я не знаю, наблюдается ли это когда-либо, когда операнд типа массива не является членом структуры или объединения, но эффекты явно демонстрируются в тех случаях, когда это так, и я не знаю ничего, что могло бы гарантировать, что подобные логики c не будет применяться к вложенным массивам.

struct foo {unsigned char x[12]};
int test1(struct foo *p1, struct foo *p2)
{
    p1->x[0] = 1;
    p2->x[1] = 2;
    return p1->x[0];
}
int test2(struct foo *p1, struct foo *p2)
{
    char *p;
    p1->x[0] = 1;
    (&p2->x[0])[1] = 2;
    return p1->x[0];
}

Код g cc, сгенерированный для test1, всегда будет возвращать 1, тогда как сгенерированный код для test2 будет возвращать то, что находится в P1-> х [0]. Я не знаю ничего в Стандарте или документации для g cc, из которого можно было бы предположить, что две функции должны вести себя по-разному, и как заставить компилятор генерировать код, который бы соответствовал случаю, когда происходят p1 и p2 идентифицировать перекрывающиеся части выделенного блока в случае необходимости. Хотя оптимизация, используемая в test1(), была бы разумной для написанной функции, я не знаю документированной интерпретации Стандарта, которая бы рассматривала этот случай как UB, но определяла бы поведение кода, если он записывает в p2->x[0] вместо p2->x[1].

0 голосов
/ 04 мая 2020

Я бы сделал a для l oop, где я установил бы i = длину вектора - 1, и каждый раз вместо того, чтобы увеличивать его, я уменьшаю его до тех пор, пока он не станет больше 0. for(int i = vet.length;i>0;i--)

...