Вопрос или проблема
Рассмотрите структуру данных для описания блоков матрицы на 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')
Мои вопросы:
- Как мне сделать это правильно?
- Верна ли моя логика и могу ли я использовать аналогичный синтаксис
.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
.
- Как мне сделать это правильно?
Сравнительно незначительное исправление заключалось бы в том, чтобы заменить ваш указатель на объединение на объединение указателей. Например:
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
.
- Верна ли моя логика и могу ли я использовать аналогичный синтаксис
.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 };
}
Ответы на ваши вопросы
-
Как сделать это правильно?
Вышеуказанный код представляет собой исправленную версию вашей структуры и правильный способ её инициализации. Замена указателя на объединение на объединение указателей решает проблему неявного преобразования типов. -
Правильна ли логика, и могу ли я использовать аналогичный синтаксис
.a.i = &B[k*P*Q]
для матрицы B типаint
?
Да, вы можете использовать инициализациюblk[k]
следующим образом:.a.as_int = &B[k * P * Q]
, когда матрицаB
— это массив целых чисел. Это позволит вам использовать либоas_int
, либоas_double
в зависимости от типа данных, с которым вы работаете.
Заключение
Использование объединений в C может значительно упростить работу с различными типами данных в одном и том же коде. Обратите внимание на необходимость правильного выбора структуры и типов данных, чтобы избежать ошибок компиляции и неясностей. Настоятельно рекомендуется тщательно тестировать вашу реализацию, чтобы убедиться в правильности работы вашего кода.
Если у вас будут дополнительные вопросы или потребуется ещё какая-либо помощь, не стесняйтесь обращаться!