Как использовать объединения в C для псевдополиморфизма

Вопрос или проблема

Рассмотрите структуру данных для описания блоков матрицы на C:

// Блок матрицы
typedef struct {
    union {
        int    i;
        double d;
    } *a;       // указатель на первый элемент блока матрицы
    int  k;     // индекс блока
    int  p;     // количество строк блока
    int  q;     // количество столбцов блока
    int  m;     // количество строк матрицы
    int  n;     // количество столбцов матрицы
    int  r;     // ранг MPI владельца блока
} mtrx_blk;

Я бы хотел использовать объединение для указателя на первый элемент блока матрицы. Я бы хотел использовать ту же структуру mtrx_blk для разных типов матриц. В моем случае матрицы могут быть либо типа double, либо int. Например, если я создаю два блока матрицы с NBLK = 2; и матрицей A типа double:

// для каждого блока
for (int k = 0; k < NBLK; k++) {

    blk[k] = (mtrx_blk) { .a = &A[k*P*Q], .k = k, .p = P, .q = Q, .m = M, .n = N, .r = k };
    }

Матрица A имеет размер M x N элементов, а блоки матрицы имеют размер P x Q элементов.

Вышеуказанный способ присвоения указателю .a приводит к ошибке:

gcc -std=c11 -pedantic-errors struct-union.c -o struct-union
struct-union.c: В функции 'main':
struct-union.c:52:36: ошибка: инициализация 'union <анонимный> *' из несовместимого типа указателя 'double *' [-Wincompatible-pointer-types]
   52 |         blk[k] = (mtrx_blk) { .a = &A[k*P*Q], .k = k, .p = P, .q = Q, .m = M, .n = N, .r = k };
      |                                    ^
struct-union.c:52:36: примечание: (вблизи инициализации для '(анонимный).a')

Я пытался сформулировать это как:

// для каждого блока
for (int k = 0; k < NBLK; k++) {

    blk[k] = (mtrx_blk) { .a.d = &A[k*P*Q], .k = k, .p = P, .q = Q, .m = M, .n = N, .r = k };
    }

Но это также не компилируется с

gcc -std=c11 -pedantic-errors struct-union.c -o struct-union
struct-union.c: В функции 'main':
struct-union.c:52:31: ошибка: имя поля не входит в инициализатор записи или объединения
   52 |         blk[k] = (mtrx_blk) { .a.d = &A[k*P*Q], .k = k, .p = P, .q = Q, .m = M, .n = N, .r = k };
      |                               ^
struct-union.c:52:31: примечание: (вблизи инициализации для '(анонимный)')
struct-union.c:52:38: ошибка: инициализация 'union <анонимный> *' из несовместимого типа указателя 'double *' [-Wincompatible-pointer-types]
   52 |         blk[k] = (mtrx_blk) { .a.d = &A[k*P*Q], .k = k, .p = P, .q = Q, .m = M, .n = N, .r = k };
      |                                      ^
struct-union.c:52:38: примечание: (вблизи инициализации для '(анонимный).a')

Мои вопросы:

  1. Как мне сделать это правильно?
  2. Верна ли моя логика и могу ли я использовать аналогичный синтаксис .a.i = &B[k*P*Q] для матрицы B целочисленного типа данных?

Пожалуйста, найдите полный исходный код ниже:

#include <stdlib.h>

// Блок матрицы
typedef struct {
    union {
        int    i;
        double d;
    } *a;       // указатель на первый элемент блока матрицы
    int  k;     // индекс блока
    int  p;     // количество строк блока
    int  q;     // количество столбцов блока
    int  m;     // количество строк матрицы
    int  n;     // количество столбцов матрицы
    int  r;     // ранг MPI владельца блока
} mtrx_blk;

int main(int argc, char *argv[]) {

    // количество строк матрицы
    int const M = 10;

    // количество столбцов матрицы
    int const N = 10;

    // количество блоков
    int const NBLK = 2;

    // количество строк блока
    int const P = M/NBLK;

    // количество столбцов блока
    int const Q = N;

    // выделение памяти для матрицы
    double *A = (double*) malloc(M*N*sizeof(double));

    // выделение массива блоков
    mtrx_blk *blk = (mtrx_blk*) malloc(NBLK*sizeof(mtrx_blk));

    // для каждого блока
    for (int k = 0; k < NBLK; k++) {

        blk[k] = (mtrx_blk) { .a.d = &A[k*P*Q], .k = k, .p = P, .q = Q, .m = M, .n = N, .r = k };
    }

    // освобождение памяти массива блоков
    free(blk);

    // освобождение памяти матрицы
    free(A);

    return 0;
}

Вместо указателя на объединение используйте объединение указателей.

typedef struct {
    union {
        int    *i;
        double *d;
    } a;        // указатель на первый элемент блока матрицы
    int  k;     // индекс блока
    int  p;     // количество строк блока
    int  q;     // количество столбцов блока
    int  m;     // количество строк матрицы
    int  n;     // количество столбцов матрицы
    int  r;     // ранг MPI владельца блока
} mtrx_blk;

Я бы хотел использовать объединение для указателя на первый элемент блока матрицы. Я бы хотел использовать ту же структуру mtrx_blk для разных типов матриц. В моем случае матрицы могут быть либо типа double, либо int.

В этом случае вы неправильно выразили свои намерения.

Это …

    union {
        int    i;
        double d;
    } *a

… объявляет a как указатель на объединение. Это будет соответствовать массиву таких объединений,* и, вероятно (хотя и не обязательно), будет использоваться с массивом double. Но вряд ли это уместно для использования с массивом int, учитывая вероятность того, что представление int в вашей системе меньше, чем представление double.

  1. Как мне сделать это правильно?

Сравнительно незначительное исправление заключалось бы в том, чтобы заменить ваш указатель на объединение на объединение указателей. Например:

typedef struct {
    union {
        int    *as_int;
        double *as_double;
    } a;        // указатель на первый элемент блока матрицы
    int  k;     // индекс блока
    int  p;     // количество строк блока
    int  q;     // количество столбцов блока
    int  m;     // количество строк матрицы
    int  n;     // количество столбцов матрицы
    int  r;     // ранг MPI владельца блока
} mtrx_blk;

Тогда вы могли бы написать что-то вроде:

    blk[k] = (mtrx_blk) { .a = { .as_double = &A[k*P*Q] }, .k = k, .p = P, .q = Q, .m = M, .n = N, .r = k };

В качестве альтернативы, да, вы можете немного упростить это до

    blk[k] = (mtrx_blk) { .a.as_double = &A[k*P*Q], .k = k, .p = P, .q = Q, .m = M, .n = N, .r = k };

Это по крайней мере допустимая форма. Я не могу оценить, правильны ли детали для того, что вы пытаетесь достичь.

Существуют и другие возможности, некоторые из которых могут лучше служить вашим целям. Например, если вы структурируете это как объединение в основном согласованных, отмеченных или определенных с помощью typedef структур, тогда у вас может быть отдельный тип для каждого типа элемента, но вы сможете обрабатывать их совместно через объединение. Это может открыть некоторую удобство через макросы типа _Generic.

  1. Верна ли моя логика и могу ли я использовать аналогичный синтаксис .a.i = &B[k*P*Q] для матрицы B целочисленного типа данных?

Да, вы можете инициализировать член int * объединения вместо члена double *.


*при соблюдении некоторых условий, связанных с областью действия типа объединения

Ответ или решение

Чтобы использовать объединения в C для псевдополиморфизма, необходим правильный подход к структурированию вашего кода, особенно когда дело касается работы с разными типами данных, такими как int и double. Рассмотрим, как можно исправить вашу текущую реализацию и ответить на ваши вопросы.

Исправление структуры mtrx_blk

Первоначально указатель a в вашей структуре mtrx_blk определён как указатель на объединение, что ведет к путанице. Чтобы правильно использовать объединение для указателей на элементы матричных блоков, вам следует изменить определение следующим образом:

typedef struct {
    union {
        int    *as_int;   // Указатель на int
        double *as_double; // Указатель на double
    } a; // Объединение указателей
    int k; // Индекс блока
    int p; // Количество строк блока
    int q; // Количество столбцов блока
    int m; // Количество строк матрицы
    int n; // Количество столбцов матрицы
    int r; // MPI ранг владельца блока
} mtrx_blk;

Пример инициализации структуры

Для инициализации массивов блоков матрицы можно использовать следующую конструкцию:

// Для каждого блока
for (int k = 0; k < NBLK; k++) {
    blk[k] = (mtrx_blk) { .a.as_double = &A[k * P * Q], .k = k, .p = P, .q = Q, .m = M, .n = N, .r = k };
}

Здесь as_double используется для указания на нужный тип данных. Если у вас есть другая матрица, состоящая из целых чисел, вы можете сделать то же самое с использованием as_int:

// Пример для матрицы целых чисел
int *B = (int*) malloc(M * N * sizeof(int));

// Для каждого блока
for (int k = 0; k < NBLK; k++) {
    blk[k] = (mtrx_blk) { .a.as_int = &B[k * P * Q], .k = k, .p = P, .q = Q, .m = M, .n = N, .r = k };
}

Ответы на ваши вопросы

  1. Как сделать это правильно?
    Вышеуказанный код представляет собой исправленную версию вашей структуры и правильный способ её инициализации. Замена указателя на объединение на объединение указателей решает проблему неявного преобразования типов.

  2. Правильна ли логика, и могу ли я использовать аналогичный синтаксис .a.i = &B[k*P*Q] для матрицы B типа int?
    Да, вы можете использовать инициализацию blk[k] следующим образом: .a.as_int = &B[k * P * Q], когда матрица B — это массив целых чисел. Это позволит вам использовать либо as_int, либо as_double в зависимости от типа данных, с которым вы работаете.

Заключение

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

Если у вас будут дополнительные вопросы или потребуется ещё какая-либо помощь, не стесняйтесь обращаться!

Оцените материал
Добавить комментарий

Капча загружается...