C语言中的指向二维数组的指针

48

我知道有几个关于如何实现这个目标的好(并且有效的)解决方案的问题,但在我看来没有一个清楚地说明最佳方法是什么。

那么,假设我们有一个二维数组:

int tab1[100][280];

我们想要创建一个指向这个二维数组的指针。为了实现这个目标,我们可以执行以下操作:

int (*pointer)[280]; // pointer creation
pointer = tab1; //assignation
pointer[5][12] = 517; // use
int myint = pointer[5][12]; // use

或者,另外一种选择:

int (*pointer)[100][280]; // pointer creation
pointer = &tab1; //assignation
(*pointer)[5][12] = 517; // use
int myint = (*pointer)[5][12]; // use 

好的,两种方法都看起来很好。现在我想知道:

  • 哪种方式是最好的,第一种还是第二种?
  • 对于编译器来说,它们都是相等的吗?(速度、性能等方面)
  • 这两种解决方案中,是否有一种比另一种占用更多的内存?
  • 开发人员更频繁使用哪种方式?

2
请记住,指针数组(例如 int *pointer[280];)与数组的数组不是同一种东西。 - Some programmer dude
1
你好,费迪南德。我已经用括号纠正了第一个解决方案。抱歉。 - Chrysotribax
为了消除在赋值步骤中的gcc警告,我需要分别使用以下转换:pointer = (int(*)[280])tab1pointer = (int(*)[100][280])&tab1 - undefined
4个回答

44
//defines an array of 280 pointers (1120 or 2240 bytes)
int  *pointer1 [280];

//defines a pointer (4 or 8 bytes depending on 32/64 bits platform)
int (*pointer2)[280];      //pointer to an array of 280 integers
int (*pointer3)[100][280]; //pointer to an 2D array of 100*280 integers

使用pointer2pointer3进行操作会产生相同的二进制结果,正如 WhozCraig 所指出的,除非进行++pointer2操作。

我建议使用typedef(产生与上述pointer3相同的二进制代码)。

typedef int myType[100][280];
myType *pointer3;

注意:自C++11版本起,你还可以使用关键字using来代替typedef

using myType = int[100][280];
myType *pointer3;

在你的例子中:

myType *pointer;                // pointer creation
pointer = &tab1;                // assignation
(*pointer)[5][12] = 517;        // set (write)
int myint = (*pointer)[5][12];  // get (read)
注意: 如果在函数体内使用数组tab1,该数组将被放置在调用堆栈内存中。但是,堆栈的大小是有限的。使用比空闲内存堆栈还要大的数组会产生 堆栈溢出 崩溃

完整代码片段可在gcc.godbolt.org上进行在线编译。

int main()
{
    //defines an array of 280 pointers (1120 or 2240 bytes)
    int  *pointer1 [280];
    static_assert( sizeof(pointer1) == 2240, "" );

    //defines a pointer (4 or 8 bytes depending on 32/64 bits platform)
    int (*pointer2)[280];      //pointer to an array of 280 integers
    int (*pointer3)[100][280]; //pointer to an 2D array of 100*280 integers  
    static_assert( sizeof(pointer2) == 8, "" );
    static_assert( sizeof(pointer3) == 8, "" );

    // Use 'typedef' (or 'using' if you use a modern C++ compiler)
    typedef int myType[100][280];
    //using myType = int[100][280];

    int tab1[100][280];

    myType *pointer;                // pointer creation
    pointer = &tab1;                // assignation
    (*pointer)[5][12] = 517;        // set (write)
    int myint = (*pointer)[5][12];  // get (read)

    return myint;
}

你好,请审核我的第一个解决方案:我之前在指针创建时忘记了括号。抱歉。 - Chrysotribax
@user216993 在第二个版本中,您还写成了(pointer*)而不是(*pointer)。好的,我会更新我的答案。干杯 - oHo
感谢olibre清晰的回答。(再次为打字错误道歉...) - Chrysotribax
使用 pointer2pointer3 产生相同的二进制是错误的,特别是在执行指针运算时。++pointer2 将使指针前进一行,而 ++pointer3 将使指针前进整个矩阵。它们是不同类型的指针。 - WhozCraig
你说得对,@WhozCraig,我的句子是错误的。感谢您的反馈 :) 我会修改它。干杯 ;) - oHo
谢谢澄清。顺便加一分。 - WhozCraig

13

你提供的两个例子是等价的。然而,第一个例子不太明显且更加“hacky”,而第二个则清楚地表明了你的意图。

int (*pointer)[280];
pointer = tab1;

指针 指向一个包含280个整数的一维数组。在你的赋值中,实际上分配了tab1的第一个。这是行得通的,因为可以将数组隐式转换为指针(指向第一个元素)。

当你使用pointer [5] [12]时,C将pointer视为数组的数组(pointer [5]的类型为int [280]),因此在这里还有另一个隐式转换(至少在语义上是这样)。

在你的第二个示例中,你明确创建了一个指向2D数组的指针:

int (*pointer)[100][280];
pointer = &tab1;

这里的语义更清晰:*pointer 是一个二维数组,所以你需要使用 (*pointer)[i][j] 访问它。

这两种解决方案使用相同数量的内存(1个指针),并且很可能运行速度也相同。在底层,这两个指针甚至将指向相同的内存位置(tab1 数组的第一个元素),你的编译器甚至可能会生成相同的代码。

第一个解决方案更加“高级”,因为需要对 C 中数组和指针的工作方式有相当深入的了解才能理解发生了什么。第二个则更加明确。


感谢您的回复,Ferdinand。我同意您的观点:第二种解决方案似乎更加明确。我将继续使用它。 - Chrysotribax

10

int *pointer[280]; //创建了280个 int 类型的指针。

在32位操作系统中,每个指针占据4字节。所以4 * 280 = 1120字节。

int (*pointer)[100][280]; //只创建一个指向 [100][280] 的整数数组的指针。

这里只有4个字节。

回到你的问题,int (*pointer)[280];int (*pointer)[100][280]; 虽然指向相同的 [100][280] 二维数组,但它们是不同的。

因为如果增加 int (*pointer)[280]; 的值,它将指向下一个1D数组,但是 int (*pointer)[100][280]; 跨越整个2D数组并指向下一个字节。访问该字节可能会导致问题,如果该内存不属于您的进程。


你好,我再次来信,请查看我的第一个解决方案:我之前在指针创建时忘记了括号。抱歉。 - Chrysotribax

0

好的,这实际上是四个不同的问题。我会逐一回答:

编译器对两者都相等吗?(速度、性能...)

是的。指针解引用和从类型 int (*)[100][280] 转换为 int (*)[280] 对于您的 CPU 来说始终是无操作的。我不会认为一个糟糕的编译器会生成虚假代码,但一个优化良好的编译器应该将两个示例编译为完全相同的代码。

其中一个解决方案比另一个占用更多内存吗?

作为我第一个答案的推论,不会。

开发人员更常使用哪种变体?

毫无疑问是没有额外的 (*pointer) 解引用的变体。对于 C 程序员来说,假设任何指针实际上可能是数组的第一个元素是天经地义的。

哪种方式最好,第一种还是第二种?

这取决于您要进行的优化:

  • 习惯用法的代码使用变体1。声明中缺少外部维度,但所有使用方式都与C程序员所期望的完全一致。

  • 如果你想明确指出你正在指向一个数组,可以使用变体2。然而,许多经验丰富的C程序员会认为在最内层的*后面隐藏着第三个维度。在那里没有数组维度会让大多数程序员感到奇怪。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接