[英]2D array seg fault in C
我正在嘗試取消引用函數islandPerimeter
的二維數組。
但我不明白為什么我會因此出現段錯誤。
有人可以指出我到底做錯了什么嗎?
更新:所以這是我試圖解決的 leetcode 問題的一部分。我現在明白它不是二維數組而是一個指針。 我仍然對 int** 感到困惑。 有人可以解釋一下嗎?
#include <stdio.h>
int islandPerimeter(int** grid, int gridSize, int gridColSize)
{
int perimeter=0,points=4,i=0;
for(int row=0;row<gridSize;++row)
{
for(int col=0;col<gridColSize;++col)
{
printf("%d ",grid[row][col]);
}
}
return perimeter;
}
int main()
{
int arr[4][5] = {{8,1,0,0,0},
{1,1,1,0,0},
{0,1,0,0,0},
{1,1,0,0,0}};
islandPerimeter(arr,4,5);
return 0;
}
int** grid
是一個指向 int 指針的指針。 它缺少數組寬度的信息。
從 C99 或 C11 開始,帶有可選的可變長度數組:
// int islandPerimeter(int** grid, int gridSize, int gridColSize)
int islandPerimeter(int gridSize, int gridColSize, int grid[gridSize][gridColSize]) {
int perimeter=0;
for(int row=0;row<gridSize;++row) {
for(int col=0;col<gridColSize;++col) {
printf("%d ",grid[row][col]);
}
}
return perimeter;
}
與
islandPerimeter(4, 5, arr);
指向數組的指針
數組是 C 中的一種不同類型。它是給定類型元素的順序集合。 在 C 中,二維數組實際上是一維數組的數組。 在你的情況下,你有一個數組[4]的int [5]
例如4 - 5-元件陣列int
通常稱為的2D陣列int
)
新程序員通常感到困惑的地方是在訪問時如何處理數組。 當訪問數組時,它被轉換為指向第一個元素的指針。 C11 標准 - 6.3.2.1 其他操作數 - 左值、數組和函數指示符(p3) (注意 4 個例外)
在一維數組的情況下,很簡單,數組被轉換為指向數組第一個元素的指針(指針只是int*
)。 在二維數組的情況下,同樣適用,數組被轉換為指向第一個元素的指針——但第一個元素是一個 5-int 的一維數組。 (指針是int [5]
數組的指針,形式上是int (*)[5]
)
您可以將二維數組(在您的情況下)作為int grid[4][5]
、 int grid[][5]
傳遞,或者反映該數組已轉換為指向第一個元素int (*grid)[5]
的指針int (*grid)[5]
。 關鍵是您必須始終為數組提供最終維度中的元素數(在此處不相關的情況下允許額外的'*'
) 5
(或元素數)必須是編譯時已知的integer constant
時間,除非使用可變長度數組(VLA),這是單獨討論的主題。
訪問數組時將其轉換為指向其第一個元素的指針的相同規則適用於數組中的每個維度,無論是 2D 數組還是 6D 數組。 C11 標准 - 6.5.2.1 數組下標(p3)
此外,知道一個指針到陣列之間(例如差int (*grid)[5]
和陣列的指針(例如int *grid[5]
由於C Operator Precedence需要括號,在這種情況下[..]
具有比'*'
更高的優先級,因此要求*grid
(在int *grid[5]
)被評估為指針(而不是作為一個數組grid[5]
) 括起來是括號(*grid)
。 從而導致int [5]
的指針數組,( int (*grid)[5]
)而不是int *grid[5]
指向int
(其中 5 個)的指針數組。
指向指針的指針
將其與指向指針的指針(例如int **
,通常稱為雙指針)進行對比。 您有兩個**
表示的兩級間接。 指針本身是一個單指針——指向什么? (另一個指針,而不是數組)。 您通常會通過首先分配一塊內存來保存一定數量的指針來使用雙指針,例如當您動態分配未知數量的已分配對象時。 這可以是未知數量的int
行和未知數量的列,也可以是未知數量的字符串,或未知數量的結構等。關鍵是您的第一級間接指向包含指針的內存。
然后對於每個可用的指針,您可以分配一個塊(例如,在您的情況下,要保存 5 個int
,然后將該內存塊的起始地址分配給您的第一個可用指針)。 您繼續為您的列(或字符串或結構)分配並按順序為每個可用指針分配起始地址。 完成后,您可以使用與 2D 數組相同的索引來訪問已分配集合中的各個元素。 這種集合和二維數組之間的區別是每個指針指向的內存在內存中不需要是連續的。
告訴他們分開
知道使用哪個的關鍵是問“我的指針指向什么? ”它是否指向一個指針? 或者,它是否指向一個數組? 如果它指向另一個指針,那么您就有了一個指向指針的指針。 如果指向的東西是一個數組,那么你就有了一個指向數組的指針。 有了這個,你就知道你需要什么作為參數。
為什么帶有 int** 的 SegFault
類型控制指針運算。 回想一下上面, int**
是一個指向指針的指針,那么指針有多大呢? ( sizeof (a_pointer)
- 通常在 x86_64 上為 8 字節,或在 x86 上為 4 字節)。 因此grid[1][0]
距離grid[0][0]
僅一個指針(8 字節)。 指向數組的指針呢? 第一個索引中的每個增量都是與第一個分開的sizeof (int[5])
。 因此,在 4x5 數組的情況下, grid[1][0]
是5 * sizeof(int)
(20 字節),除了grid[0][0]
。
因此,當嘗試訪問數組數組時,使用int**
,從grid[1][3]
(或 32 位框上的grid[1][4]
)開始,您正在閱讀第一行值。 (您偏移了 8 個字節(一個指針 8 個字節 - 跳過 2-int),將您放在第一行中的第 3 個整數之前,然后再偏移 3 個整數,將您置於grid[0][5]
超過第一行grid[0][4]
的最后一個值。(這與每行增量復合)結果未定義,任何事情都可能發生。
當您傳遞適當的pointer-to-array 時,行索引偏移量的每個增量都會增加 20 字節,將您置於下一個一維值數組的開頭,因此對每一列的迭代保持在該一維數組的范圍內。
仔細考慮一下,如果您還有其他問題,請告訴我,我很樂意為您提供進一步幫助。
嘗試這個
int islandPerimeter(int* grid, int gridSize, int gridColSize) {
int perimeter = 0, points = 4, i = 0;
for(int row=0; row < gridSize; ++row) {
for(int col = 0; col < gridColSize; ++col) {
printf("%d ",grid[row*gridColSize + col]);
}
}
return perimeter;
}
您必須將呼叫更改為
islandPerimeter((int *)grid, 4, 5);
假設您想保持函數不變,而是更改在 main(或任何其他調用函數)中初始化 2D 數組的方式。 如果數組數據由用戶輸入或在運行時從文件加載,這也是您必須執行的操作,因此了解以下信息很有用:
int main(void) {
const int ROWS = 4; //these don't have to be const;
const int COLS = 5;
const int data[20] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20};
int** pointer_arr = malloc(ROWS * sizeof(int*)); //allocate space for each ptr
//error check
if (pointer_arr == NULL) {
printf("Unsuccessful ptr-ptrarray allocation attempt\n");
exit(0);
}
for (int i = 0; i < ROWS; ++i) {
pointer_arr[i] = malloc(COLS * sizeof(int)); //allocate space for each int
//error check with alternative indexing syntax (same as pointer_arr[i])
if (*(pointer_arr + i) == NULL) {
printf("Unsuccessful ptr-intarray allocation attempt\n");
exit(0);
}
}
//load each allocated int address space with an int from data:
for (int i = 0; i < ROWS ; ++i) {
for (int j = 0; j < COLS; ++j) {
pointer_arr[i][j] = data[ROWS * i + j];
}
}
//Now you can call your unaltered function and it will perform as expected:
islandperimeter(pointer_arr, ROWS, COLS);
return 0;
}
在正常情況下(當程序不會立即終止時)請注意,您必須手動釋放所有分配的內存,否則會出現內存泄漏。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.