簡體   English   中英

如何在 C 的結構數組中使用動態二維數組?

[英]How to use dynamic 2d array inside a struct array in C?

我想做什么?

我正在做一個關於動態矩陣乘法的項目。 我想從用戶那里輸入他/她想要執行乘法的矩陣數量,並基於此我想創建一個如下所示的結構:

typedef struct
{
    size_t rows, columns;
    int table[];
} Matrix;

之后,檢查要相乘的矩陣的有效性(使用非常簡單的數學)

然后創建一個Matrix類型的數組,根據用戶要相乘的矩陣個數分配memory給它。

+---------------------------------------------------------------------------------------------------+
|    +------------------------------+  +---------------------------------------------------------+  |
|    |   Matrix struct type array   |  |  ..... More arrays depending on the number of matrices. |  |
|    +------------------------------+  +---------------------------------------------------------+  |
+---------------------------------------------------------------------------------------------------+



Eg. 2 Matrices [2][2] & [2][1]

    +----------------> rows <-------------+      
    |                                     |              
    |  +------------> columns <-----------|--+
    |  |                                  |  |
    |  |  +------------------------+      |  |  +------------------+
{ { 2, 2, | { { 1, 2 }, { 2, 1 } } | }, { 2, 1, | { { 1 }, { 3 } } | } } 
          +------------------------+            +------------------+
                      |                                   |
                      |                                   |
                      |                                   |
                      v                                   v
                  | 1   2 |                             | 1 |
                  |       |                             |   | 
                  | 2   1 |                             | 3 |

創建結構類型數組的原因

對於這個問題的一些讀者來說,有一件事可能看起來很奇怪,那就是,為什么我想制作一個類型為struct Matrix的數組,而不是創建 2 個不同類型的對象並在"user_defined_matrix"->table上執行乘法"user_defined_matrix"->table 那么,原因如下:

  • 可擴展性,因為每個約束都是用戶定義的,我希望事情盡可能靈活。 一種思考方式是,假設用戶想要 15 個矩陣之間的乘法,那么您不想聲明 15 個類型為struct Matrix的對象。
  • 訪問性,在 for 循環的幫助下訪問每個矩陣將變得如此容易。

言歸正傳,分配完memory后,我想讓用戶填滿矩陣的每個槽位,然后對它們進行乘法運算,得到結果。


到目前為止我做了什么

注意:我知道你不能在結構中聲明一個真正的二維 VLA,你必須首先聲明一個一維數組,它后來變成一個二維數組的損壞版本,然后在使用它之前將它強制轉換為一個二維數組,如這個答案所示。

我制作了一個真正的二維數組來存儲用戶輸入的行和列。

int dimensions[NUMBER_OF_MATRIX][2];

然后使用這些dimensions ,我計算了我必須分配多少 memory。

int total_matrix_size = 0;
for (uint i = 0; i < NUMBER_OF_MATRIX; i++)
{
    total_matrix_size += (dimensions[i][0] * dimensions[i][1]);
}

然后使用total_matrix_size將 memory 分配給類型數組struct Martix

Matrix *matrix = malloc(((sizeof *matrix) * NUMBER_OF_MATRIX) + sizeof(int[total_matrix_size]));

之后,我要求用戶填充矩陣,在填充矩陣之前,我借助下面代碼中的宏將數組轉換為二維數組。

注意:我將多次引用下面的代碼塊,為了簡單起見,我們將此命名為輸入塊

#define get_array(arr) \
    _Generic((arr),    \
             Matrix    \
             : (int(*)[(arr).columns])(arr).table)

for (uint i = 0; i < NUMBER_OF_MATRIX; i++)
        {
            matrix[i].rows = dimensions[i][0];
            matrix[i].columns = dimensions[i][1];

            for (uint x = 0; x < matrix[i].rows; x++)
            {
                for (uint y = 0; y < matrix[i].columns; y++)
                {
                    printf("Enter values of matrix %d a[%dx%d] : ", i + 1, x + 1, y + 1);
                    scanf("%d", &get_array(matrix[i])[x][y]);
                    printf("%d\n", get_array(matrix[i])[x][y]); // print statement 1
                }
            }
        }

然后出於測試目的,我正在打印所有矩陣。

注意:我將多次引用下面的代碼塊,為了簡單起見,我們將此塊命名為output 塊

for (uint i = 0; i < NUMBER_OF_MATRIX; i++)
        {
            printf("Matrix %d\n", i+1);
            for (uint x = 0; x < matrix[i].rows; x++)
            {
                for (uint y = 0; y < matrix[i].columns; y++)
                {

                    printf("%d ", get_array(matrix[i])[x][y]); // print statement 2
                }
                printf("\n");
            }
        }

那么,問題出在哪里呢?

如您所見,我在上面的 2 個代碼塊inputoutput塊中編寫了 2 個完全相同的打印語句。

printf("%d ", get_array(matrix[i])[x][y]);

但它們都產生不同的輸出。 Output 由輸入塊生成。

Enter the rows of matrix 1 : 2 2
Enter the rows of matrix 2 : 2 2
Enter values of matrix 1 a[1x1] : 1
1
Enter values of matrix 1 a[1x2] : 0
0
Enter values of matrix 1 a[2x1] : 1
1
Enter values of matrix 1 a[2x2] : 0
0
Enter values of matrix 2 a[1x1] : 2
2
Enter values of matrix 2 a[1x2] : 3
3
Enter values of matrix 2 a[2x1] : 2
2
Enter values of matrix 2 a[2x2] : 3
3

按預期打印所有內容。

output 塊不一樣。

Matrix 1
2 2
2 3
Matrix 2
2 3
2 3

我對答案有什么期望?

只不過是對以下內容的深入解釋:

  • 為什么 print 語句在輸入塊中起作用,為什么在output 塊中不起作用。
  • 如果這不是分配 memory 的正確方法,那什么才是?
  • 如果我的方法完全錯誤,那應該是什么?

請保持格式有點像這個答案


這是整個代碼

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

// TODO Preprocessors
#define get_array(arr) \
    _Generic((arr),    \
             Matrix    \
             : (int(*)[(arr).columns])(arr).table)

// TODO Custom types
typedef unsigned int uint;

// TODO Structs
typedef struct
{
    uint rows, columns;
    int table[];
} Matrix;

// TODO Function Declarations
void flushBuffer(void);

int main(void)
{
    int NUMBER_OF_MATRIX = 2;
    int dimensions[NUMBER_OF_MATRIX][2];

    for (uint i = 0; i < NUMBER_OF_MATRIX; i++)
    {
        printf("Enter the rows of matrix %d : ", i + 1);
        scanf("%d %d", &dimensions[i][0], &dimensions[i][1]);
        flushBuffer();
    }

    if (dimensions[0][1] != dimensions[1][0])
    {
        printf("Matrix multiplication not possible.");
    }
    else
    {
        int total_matrix_size = 0;
        for (uint i = 0; i < NUMBER_OF_MATRIX; i++)
        {
            total_matrix_size += (dimensions[i][0] * dimensions[i][1]);
        }

        Matrix *matrix = malloc(((sizeof *matrix) * NUMBER_OF_MATRIX) + sizeof(int[total_matrix_size]));

        for (uint i = 0; i < NUMBER_OF_MATRIX; i++)
        {
            matrix[i].rows = dimensions[i][0];
            matrix[i].columns = dimensions[i][1];

            for (uint x = 0; x < matrix[i].rows; x++)
            {
                for (uint y = 0; y < matrix[i].columns; y++)
                {
                    printf("Enter values of matrix %d a[%dx%d] : ", i + 1, x + 1, y + 1);
                    scanf("%d", &get_array(matrix[i])[x][y]);
                    printf("%d\n", get_array(matrix[i])[x][y]);
                }
            }
        }

        for (uint i = 0; i < NUMBER_OF_MATRIX; i++)
        {
            printf("Matrix divider\n");
            for (uint x = 0; x < matrix[i].rows; x++)
            {
                for (uint y = 0; y < matrix[i].columns; y++)
                {

                    printf("%d ", get_array(matrix[i])[x][y]);
                }
                printf("\n");
            }
        }
    }

    return 0;
}

// TODO Function Definitions
void flushBuffer(void)
{
    int c;
    while ((c = getchar()) != '\n' && c != EOF)
        ;
}

我將從開門見山開始。 我將詳細介紹您之后要求的特定主題。

解決方案

問題

簡而言之,您對問題的理論方法是合理的 IMO。 但是 memory 管理沒有正確實施。

您想要做的是擁有Matrix結構的多個實例。 這不是您當前設置的結果。 具體來說:

Matrix *matrix = malloc(((sizeof *matrix) * NUMBER_OF_MATRIX) + sizeof(int[total_matrix_size]));

當然,您在這里為NUMBER_OF_MATRIX Matrix實例分配了足夠的 memory。 但是你這樣做是為了Matrix類型。 給定matrixpointer to Matrix矩陣的指針,並且sizeof *matrix (或sizeof(Matrix) )等於sizeof(int[2]) (1) ,當您索引該數組(即進行指針算術)時,memory 偏移量增量就是那個尺寸。

假設sizeof(int) = 4NUMBER_OF_MATRIX = 2total_matrix_size = 8 (2 個2x2矩陣)。 如果給matrix memory 地址0x0010&(matrix[1])將是0x0018 ,而不是你期望的0x0028 (24 字節偏移量)。

有關 C 如何處理指針運算的更多參考,您可以參考此 SO 答案這篇文章,其中對該主題有更詳細的概述。

IMO 實現你想要的最干凈的方法是動態分配matrix[i].table單獨而不是預先分配你需要的所有 memory 一次。 這是出於以下幾個原因:

  1. 這對其他讀者和你自己來說會更干凈(發生這種問題的可能性較小)
  2. 隨着您需要的總數 memory 變得越來越大,操作系統越來越難以處理該單個分配,因為它試圖找到 memory 的連續部分。這對您的應用程序來說可能是也可能不是問題,但這是一個重點無論如何都要觀看。 有關此事的更深入概述,請參閱本系列文章(2)

(1):我正在考慮給定的結構示例,其中只有int s。 這意味着不需要填充 如果有不同類型的成員,由於添加了填充,結構的總大小可能會略大於成員大小的總和。

(2):可以證明單次分配比多次分配更快。 這通常是正確的,因為分配 memory 具有固有的開銷。 同樣,這是您需要針對您的應用程序單獨考慮的事情,而不是將一般性陳述視為事實

可能的解決方案

我能想到的讓你繼續遵循這個策略的唯一原因是,如果你出於性能原因需要這樣做,並且你想最小malloc調用(也許你工作的平台對內存分配有嚴重的懲罰)。 鑒於我可以建議修復您當前方法的一件事是將get_array宏修改為如下內容:

#define get_array(arr, idx) \
    _Generic((arr),         \
             Matrix *       \
             : (int(*)[(arr)[offset_for_index((arr), (idx))].columns])(arr)[offset_for_index((arr), (idx))].table)

並定義一個offset_for_index function(或者作為一個宏,如果你願意的話),例如:

static inline size_t offset_for_index(struct x *arr, size_t idx) {
    size_t offset = 0;

    for (size_t i = 0; i < idx; ++i) {
        offset += (sizeof *arr) + sizeof(int[arr[offset].a * arr[offset].b]);
    }

    // No need to safeguard because all our struct members are `int`, so
    // `offset` will, necessarily, be a multiple of `sizeof *arr`
    return offset / sizeof *arr;
}

但在我看來這絕對是麻煩的。 我已將其聲明為inline以嘗試遵循避免 function 調用的模式(假設您選擇將get_array定義為宏)。 並且 function 甚至可能不會被內聯,除非您嚴格強制編譯器這樣做。 有關內聯函數的更多信息,請參見此處此處

這個新設置的示例用法如下:

/* ... */

#define NUMBER_OF_MATRIX 2
#define TOTAL_TABLE_ELEMENTS 8
#define SIZE ((sizeof *a) * NUMBER_OF_MATRIX + sizeof(int[TOTAL_TABLE_ELEMENTS]))

int main() {
    Matrix *a = calloc(1, SIZE);

    a[offset_for_index(a, 0)].a = 2;
    a[offset_for_index(a, 0)].b = 2;

    a[offset_for_index(a, 1)].a = 2;
    a[offset_for_index(a, 1)].b = 2;

    for (size_t i = 0; i < 2; ++i) {
        for (size_t j = 0; j < 2; ++j) {
            get_array(a, 0)[i][j] = 10 * (i + 1);
            get_array(a, 1)[i][j] = -10 * (i + 1);
        }
    }

    // Your output block
    for (uint i = 0; i < NUMBER_OF_MATRIX; i++) {
        printf("Matrix %d\n", i+1);
        size_t idx = offset_for_index(a, i);
        for (uint x = 0; x < a[idx].rows; x++) {
            for (uint y = 0; y < a[idx].columns; y++) {
                printf("%d ", get_array(a, i)[x][y]);
            }
            printf("\n");
        }
    }

    free(a);

    return 0;
}

為什么打印語句在輸入塊中起作用?

這並不是說他們確實在工作 您只是在打印您剛剛設置的內容。 您所做的與以下內容幾乎相同:

  /* ... */
  
  int n;
  scanf("%d", &n);
  pritnf("%d\n", n);

memory的正確分配方式

只有西斯才做絕對的交易

“只有西斯才做絕對的交易” 克諾比,歐比萬。

我不是特別喜歡通過說某事是正確的方式或某事永遠不應該使用/完成來限制方法(甚至goto在 IMO 中也有它的位置)。 也就是說,我不會告訴您分配 memory 的正確方法是什么。但是,正如我之前提到的,根據我目前掌握的信息,我認為最好的方法是為table成員動態分配 memory . 您的結構定義將更新為:

typedef struct {
    uint rows, columns;
    int *table;
} Matrix;

在設置新矩陣后,您需要為table分配 memory :

Matrix *matrix = malloc((sizeof *matrix) * NUMBER_OF_MATRIX);

for (uint i = 0; i < NUMBER_OF_MATRIX; i++) {
    matrix[i].rows = dimensions[i][0];
    matrix[i].columns = dimensions[i][1];

    matrix[i].table = malloc(sizeof(int) * matrix[i].rows * matrix[i].columns);
    for (uint x = 0; x < matrix[i].rows; x++) {
        for (uint y = 0; y < matrix[i].columns; y++)
        {
            printf("Enter values of matrix %d a[%dx%d] : ", i + 1, x + 1, y + 1);
            scanf("%d", &get_array(matrix[i])[x][y]);
            printf("%d\n", get_array(matrix[i])[x][y]); // print statement 1
            // Mind `get_array` here is your original macro!
        }
    }
}

顯然,您需要在不再需要時free memory。 假設只有當您的程序完成時才會出現這種情況,以下方法可以解決問題:

for (uint i = 0; i < NUMBER_OF_MATRIX; i++) {
    free(matrix[i].table);
}

free(matrix);

我不會回答最后一個問題,因為我相信我已經通過上面的部分一遍又一遍地回答了。 簡而言之,您應該考慮您的特定需求和您認為更重要的東西(可能是更清晰的代碼,或者可能是一點——或更多——更高的性能)。

我希望這能澄清問題,您將能夠思考並為您的項目選擇最佳的前進方式。 最好的祝福!

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM