Typedef для двумерного массива в C и доступ по указателю

Я создаю собственный тип для таблицы (двумерный массив):

typedef int table_t[4][2]; // 4 rows, 2 columns

Далее я объявляю таблицу и указатель на таблицу:

table_t a = { {10, 0},    // my table
              {20, 0}, 
              {30, 0}, 
              {40, 0} };

table_t *b = &a;          // pointer to my table

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

    printf("%d\n", a[0][0]);    // var1. 
    printf("%d\n", a[1][0]);    // It's work    
    printf("%d\n", a[2][0]);
    printf("%d\n", a[3][0]);

Оно работает. Как я могу получить доступ к элементам таблицы по указателю на table_t?

Я пытаюсь напечатать первый столбец таблицы по указателю:

    printf("%d\n", *b[0][0]);   // var2. 
    printf("%d\n", *b[1][0]);   // It's print true value only from *b[0][0] and
    printf("%d\n", *b[2][0]);   // some strange from other elements
    printf("%d\n", *b[3][0]);

Может быть, я делаю что-то не так? Внезапно у меня работает следующий код, но почему таблица повернулась? :

    printf("%d\n", *b[0][0]);   // var3
    printf("%d\n", *b[0][1]);   // Suddenly, it's work too.
    printf("%d\n", *b[0][2]);   
    printf("%d\n", *b[0][3]);

Демо-версия Godbolt: https://godbolt.org/z/7bb63vGxT

*b[x][y] ==> (*b)[x][y]
mch 08.07.2024 10:13

Оператор [] группируется более плотно, чем унарный оператор *. Итак, *b[0][1] эквивалентно *(b[0][1]), а это не то, что вам нужно. Вместо этого вы хотите сначала разыменовать b, как в (*b)[0][1].

Tom Karzes 08.07.2024 10:19
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
2
85
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

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

void print_column_zero(table_t *b) {
    printf("%d\n", (*b)[0][0]);
    printf("%d\n", (*b)[1][0]);
    printf("%d\n", (*b)[2][0]);
    printf("%d\n", (*b)[3][0]);
}

[...]

    // passing a pointer to the table
    print_column_zero(&a);

Обратите внимание, что вместо указателя на table_t вы можете написать:

void print_column_zero(table_t b) {
    printf("%d\n", b[0][0]);
    printf("%d\n", b[1][0]);
    printf("%d\n", b[2][0]);
    printf("%d\n", b[3][0]);
}

[...]

    // passing a table, which decays to a pointer to its first element
    print_column_zero(a);

Поскольку table_t является определением типа для массива, это будет эквивалентно: void print_column_zero(int (*b)[2])... и print_column_zero(&a[0]), т.е. будет передан указатель на элементы таблицы, как и для void print_column_zero(table_t *b), но с типом, который не требует разыменования. Сгенерированный код должен быть очень похож, если не идентичен.

Указатели на массивы несколько сбивают с толку и редко используются на практике. Вместо этого вы можете захотеть инкапсулировать свою таблицу в структуру:

typedef struct table_t {
    int table[4][2]; // 4 rows, 2 columns
} table_t;

table_t a = {{ { 10, 0 },    // my table
               { 20, 0 }, 
               { 30, 0 }, 
               { 40, 0 } }};

void print_column_zero(table_t *b) {
    printf("%d\n", b->table[0][0]);
    printf("%d\n", b->table[1][0]);
    printf("%d\n", b->table[2][0]);
    printf("%d\n", b->table[3][0]);
}

[...]

    printf("%d\n", a.table[0][0]);
    printf("%d\n", a.table[1][0]);
    printf("%d\n", a.table[2][0]);
    printf("%d\n", a.table[3][0]);

    print_column_zero(&a);    // same output

    table_t *b = &a;          // pointer to my table
    print_column_zero(b);     // same output again

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

Спасибо, Вы правы. Я ошибся с приоритетами операций. И спасибо за совет — код со структурой понятнее.

Vyacheslav Verkhovin 08.07.2024 10:30

Скрывать массивы (или указатели) за typedef — не лучшая идея, поскольку это усложняет чтение и поддержку программы. А баги писать проще, как вы заметили.

Проблема в том, что [] имеет более высокий приоритет оператора, чем * разыменование.

  • Фактический тип bint (*)[4][2].
  • Итак, в случае *b[i][j] первый [i] разыменовывает указатель.
  • Второй [j] дает нам номер массива j с типом int [2].
  • * дает первый элемент в этом массиве, поскольку «распад массива» означает, что мы получаем здесь указатель на первый элемент.

Так, например, *b[0][1] даст второй массив типа int [2] и * первый элемент, 20. Не так, как ожидалось.

И очевидным решением было бы написать (*b)[0][0].

Лучшим решением было бы избавиться от typedef и объявить указатель как int (*b)[2] = a;. Это избавит вас от лишней косвенности, поскольку позволит вам вводить b[i][j].

(В C23 мы могли бы также несколько загадочно написать typeof (*a)* b = a;, но это сложно читать и понимать, поэтому я не рекомендую это делать.)

Lundin 08.07.2024 11:24

Или просто auto b = a;

tstanisl 08.07.2024 18:45

@tstanisl typeof (*a)* b = a; типобезопасен, auto b = a; нет и примерно так же плох, как писать void* b = a;. Что, если бы я хотел сделать &a или *a? Первый пример выдает ошибку компилятора, затем версии auto и void указателя просто весело продолжают работать, и теперь у нас есть скрытая ошибка. Изменение значения auto было одной из самых глупых ошибок C++, разработанных людьми, которые никогда не работали с высокодобросовестным программным обеспечением.

Lundin 09.07.2024 08:29

Что такого небезопасного в использовании auto b=a?

tstanisl 10.07.2024 09:34

@tstanisl В какой-то момент я хотел написать вопросы и ответы по этому поводу. Возможно, сейчас самое время :) Я разместил вопрос на Codidact Почему новое ключевое слово auto из C++11 или C23 опасно? и я туда же напишу ответ.

Lundin 10.07.2024 10:24

Спасибо за ссылку. Это очень интересно, хотя с некоторыми аргументами я не согласен. Должен признаться, он был очень удивлен разницей const auto и auto const. Я пытался протестировать его в CLANG (см. ссылку ), но похоже, что в обоих случаях тип char * const. Это ожидаемое поведение или потенциальная ошибка в CLANG?

tstanisl 12.07.2024 13:42

@tstanisl Хм, на самом деле эту часть, вероятно, можно было бы сделать с корректурой. В любом случае мы ожидаем получить const char*, но не получаем его. Посмотрим, смогу ли я придумать лучший пример.

Lundin 12.07.2024 14:07

@tstanisl Я отредактировал сообщение Codidact, чтобы сделать его менее конфронтационным, поскольку я мог перепутать его с каким-либо расширением компилятора или другим. Вернусь, когда у меня будет больше времени. В любом случае, проблема, заключающаяся в том, что мы не можем смешать auto с константной корректностью, также является достаточно серьезным недостатком, чтобы мотивировать удаление auto из языка.

Lundin 12.07.2024 14:17

Другие вопросы по теме