**变量和变量[] []之间的区别?

问题描述:

我不明白为什么我必须收到一个二维阵列的内容 b[][3]而不是**b?另外我们怎样才能按为二维数组拨打? 另外,二维数组arr的地址等于arr的内容等于*arr等于&arr[0][0];所有地址都是一样的。我无法清楚地看到它;有人可以向我解释一个多维数组是如何存储的。 “图片有用的链接将受到欢迎”。**变量和变量[] []之间的区别?

#include "hfile.h" // contains all needed H files 

void caller(int b[][3]) // why can't we write **b? 
{ 
    int k=100; 
    printf("\n****Caller:****\n"); 

    for(int i=0;i<3;i++) 
    { 
     for(int j=0;j<3;j++) 
     { 
      b[i][j]=k++; 
      printf("\t %d",b[i][j]); 
     } 
     printf("\n"); 
    } 
} 

int main() 
{ 
    int arr[3][3]={1,2,3,4,5,6,7,8,9}; // original containts 

    caller(arr);    // Called caller function passing containts of "arr" 

    printf("\n****Orignal****\n"); 
    for(int i=0;i<3;i++) 
    { 
     for(int j=0;j<3;j++) 
      printf("\t %d",arr[i][j]);   

     printf("\n"); 
    } 
    return 0; 
} 
+0

当你使用'int ** b'时会发生什么?一些编译器错误? – user1025189 2012-02-12 11:14:44

+0

是错误:无法将int [3] *转换为int ** – user1171901 2012-02-12 11:34:55

+0

请阅读[comp.lang.c FAQ](http://c-faq.com)的第6节。 – 2012-02-12 11:51:44

ASCII艺术规则!

让我们来看一个2D数组。我们假设该数组是2个字节的short整数,并且地址也是方便的2个字节。如果你喜欢,这可能是一个Zilog Z80芯片,但它只是为了方便保持数字小。

short A[3][3]; 

+---------+---------+---------+ 
| A[0][0] | A[0][1] | A[0][2] | 
+---------+---------+---------+ 
| A[1][0] | A[1][1] | A[1][2] | 
+---------+---------+---------+ 
| A[2][0] | A[2][1] | A[2][2] | 
+---------+---------+---------+ 

让我们假设地址:A = 0x4000。该阵列的元件的short *地址,然后,分别是:现在

&A[0][0] = 0x4000; 
&A[0][1] = 0x4002; 
&A[0][2] = 0x4004; 
&A[1][0] = 0x4006; 
&A[1][1] = 0x4008; 
&A[1][2] = 0x400A; 
&A[2][0] = 0x400C; 
&A[2][1] = 0x400E; 
&A[2][2] = 0x4010; 

,还应该观察到,则可以写成:

&A[0] = 0x4000; 
&A[1] = 0x4006; 
&A[2] = 0x400C; 

类型这些指针是“指针数组[3] short'或short (*A)[3]

还可以写为:

&A  = 0x4000; 

类型的,这是 '指针数组[3] [3]的short',或short (*A)[3][3]

其中一个主要的差异是在对象的大小,因为这代码演示:

#include <stdio.h> 
#include <inttypes.h> 

static void print_address(const char *tag, uintptr_t address, size_t size); 

int main(void) 
{ 
    char buffer[32]; 
    short A[3][3] = { { 0, 1, 2 }, { 3, 4, 5 }, { 6, 7, 8 } }; 
    int i, j; 

    print_address("A", (uintptr_t)A, sizeof(A)); 
    print_address("&A", (uintptr_t)&A, sizeof(*(&A))); 

    for (i = 0; i < 3; i++) 
    { 
     for (j = 0; j < 3; j++) 
     { 
      sprintf(buffer, "&A[%d][%d]", i, j); 
      print_address(buffer, (uintptr_t)&A[i][j], sizeof(*(&A[i][j]))); 
     } 
    } 

    for (i = 0; i < 3; i++) 
    { 
     sprintf(buffer, "&A[%d]", i); 
     print_address(buffer, (uintptr_t)&A[i], sizeof(*(&A[i]))); 
    } 

    putchar('\n'); 
    for (i = 0; i < 3; i++) 
    { 
     for (j = 0; j < 3; j++) 
     { 
      printf(" A[%d][%d] = %d", i, j, A[i][j]); 
     } 
     putchar('\n'); 
    } 

    return 0; 
} 

static void print_address(const char *tag, uintptr_t address, size_t size) 
{ 
    printf("%-8s = 0x%.4" PRIXPTR " (size %zu)\n", tag, address & 0xFFFF, size); 
} 

该程序假货16位地址与所述print_address()函数的掩蔽操作。

在MacOS X 10.7.2(GCC'i686-apple-darwin11-llvm-gcc-4.2(GCC)4.2.1(基于Apple Inc. build 5658)(LLVM)上编译为64位模式时的输出建立2335.15.00)'),是:

A   = 0xD5C0 (size 18) 
&A  = 0xD5C0 (size 18) 
&A[0][0] = 0xD5C0 (size 2) 
&A[0][1] = 0xD5C2 (size 2) 
&A[0][2] = 0xD5C4 (size 2) 
&A[1][0] = 0xD5C6 (size 2) 
&A[1][1] = 0xD5C8 (size 2) 
&A[1][2] = 0xD5CA (size 2) 
&A[2][0] = 0xD5CC (size 2) 
&A[2][1] = 0xD5CE (size 2) 
&A[2][2] = 0xD5D0 (size 2) 
&A[0]  = 0xD5C0 (size 6) 
&A[1]  = 0xD5C6 (size 6) 
&A[2]  = 0xD5CC (size 6) 

    A[0][0] = 0 A[0][1] = 1 A[0][2] = 2 
    A[1][0] = 3 A[1][1] = 4 A[1][2] = 5 
    A[2][0] = 6 A[2][1] = 7 A[2][2] = 8 

予编译而不在32位模式下的掩蔽操作的变体,并得到了输出:

A   = 0xC00E06D0 (size 18) 
&A  = 0xC00E06D0 (size 18) 
&A[0][0] = 0xC00E06D0 (size 2) 
&A[0][1] = 0xC00E06D2 (size 2) 
&A[0][2] = 0xC00E06D4 (size 2) 
&A[1][0] = 0xC00E06D6 (size 2) 
&A[1][1] = 0xC00E06D8 (size 2) 
&A[1][2] = 0xC00E06DA (size 2) 
&A[2][0] = 0xC00E06DC (size 2) 
&A[2][1] = 0xC00E06DE (size 2) 
&A[2][2] = 0xC00E06E0 (size 2) 
&A[0]  = 0xC00E06D0 (size 6) 
&A[1]  = 0xC00E06D6 (size 6) 
&A[2]  = 0xC00E06DC (size 6) 

    A[0][0] = 0 A[0][1] = 1 A[0][2] = 2 
    A[1][0] = 3 A[1][1] = 4 A[1][2] = 5 
    A[2][0] = 6 A[2][1] = 7 A[2][2] = 8 

而在64位模式下,该变体的输出为:

A   = 0x7FFF65BB15C0 (size 18) 
&A  = 0x7FFF65BB15C0 (size 18) 
&A[0][0] = 0x7FFF65BB15C0 (size 2) 
&A[0][1] = 0x7FFF65BB15C2 (size 2) 
&A[0][2] = 0x7FFF65BB15C4 (size 2) 
&A[1][0] = 0x7FFF65BB15C6 (size 2) 
&A[1][1] = 0x7FFF65BB15C8 (size 2) 
&A[1][2] = 0x7FFF65BB15CA (size 2) 
&A[2][0] = 0x7FFF65BB15CC (size 2) 
&A[2][1] = 0x7FFF65BB15CE (size 2) 
&A[2][2] = 0x7FFF65BB15D0 (size 2) 
&A[0]  = 0x7FFF65BB15C0 (size 6) 
&A[1]  = 0x7FFF65BB15C6 (size 6) 
&A[2]  = 0x7FFF65BB15CC (size 6) 

    A[0][0] = 0 A[0][1] = 1 A[0][2] = 2 
    A[1][0] = 3 A[1][1] = 4 A[1][2] = 5 
    A[2][0] = 6 A[2][1] = 7 A[2][2] = 8 

32位和64位地址版本中有很多噪音,所以我们可以保留“伪16位”地址版本。

请注意A[0][0]的地址与A[0]A的地址是如何相同的,但指向的对象的大小是不同的。 &A[0][0]指向单个(短)整数; &A[0]指向3个(短)整数的数组; &A指向一个3x3(短)整数数组。

现在我们需要看看short **是如何工作的;它的工作原理完全不同这里有一些测试代码,与前面的例子有关但不同。

#include <stdio.h> 
#include <inttypes.h> 

static void print_address(const char *tag, uintptr_t address, size_t size); 

int main(void) 
{ 
    char buffer[32]; 
    short t[3] = { 99, 98, 97 }; 
    short u[3] = { 88, 87, 86 }; 
    short v[3] = { 77, 76, 75 }; 
    short w[3] = { 66, 65, 64 }; 
    short x[3] = { 55, 54, 53 }; 
    short y[3] = { 44, 43, 42 }; 
    short z[3] = { 33, 32, 31 }; 
    short *a[3] = { t, v, y }; 
    short **p = a; 
    int i, j; 

    print_address("t", (uintptr_t)t, sizeof(t)); 
    print_address("u", (uintptr_t)u, sizeof(u)); 
    print_address("v", (uintptr_t)v, sizeof(v)); 
    print_address("w", (uintptr_t)w, sizeof(w)); 
    print_address("x", (uintptr_t)x, sizeof(x)); 
    print_address("y", (uintptr_t)y, sizeof(y)); 
    print_address("z", (uintptr_t)z, sizeof(z)); 

    print_address("a", (uintptr_t)a, sizeof(a)); 
    print_address("&a", (uintptr_t)&a, sizeof(*(&a))); 

    for (i = 0; i < 3; i++) 
    { 
     for (j = 0; j < 3; j++) 
     { 
      sprintf(buffer, "&a[%d][%d]", i, j); 
      print_address(buffer, (uintptr_t)&a[i][j], sizeof(*(&a[i][j]))); 
     } 
    } 

    for (i = 0; i < 3; i++) 
    { 
     sprintf(buffer, "&a[%d]", i); 
     print_address(buffer, (uintptr_t)&a[i], sizeof(*(&a[i]))); 
    } 

    putchar('\n'); 
    for (i = 0; i < 3; i++) 
    { 
     for (j = 0; j < 3; j++) 
     { 
      printf(" a[%d][%d] = %d", i, j, a[i][j]); 
     } 
     putchar('\n'); 
    } 

    putchar('\n'); 
    print_address("p", (uintptr_t)p, sizeof(*(p))); 
    print_address("&p", (uintptr_t)&p, sizeof(*(&p))); 

    for (i = 0; i < 3; i++) 
    { 
     for (j = 0; j < 3; j++) 
     { 
      sprintf(buffer, "&p[%d][%d]", i, j); 
      print_address(buffer, (uintptr_t)&p[i][j], sizeof(*(&p[i][j]))); 
     } 
    } 

    for (i = 0; i < 3; i++) 
    { 
     sprintf(buffer, "&p[%d]", i); 
     print_address(buffer, (uintptr_t)&p[i], sizeof(*(&p[i]))); 
    } 

    putchar('\n'); 
    for (i = 0; i < 3; i++) 
    { 
     for (j = 0; j < 3; j++) 
     { 
      printf(" p[%d][%d] = %d", i, j, p[i][j]); 
     } 
     putchar('\n'); 
    } 

    return 0; 
} 

static void print_address(const char *tag, uintptr_t address, size_t size) 
{ 
    printf("%-8s = 0x%.4" PRIXPTR " (size %zu)\n", tag, address & 0xFFFF, size); 
} 

这是一个两半的程序。一半解剖阵列a;另一个解剖双指针p。下面是一些ASCII艺术来帮助理解这一点:

+------+------+------+      +------+------+------+ 
| 99 | 98 | 97 | t = 0x1000  | 88 | 87 | 86 | u = 0x1100 
+------+------+------+      +------+------+------+ 

+------+------+------+      +------+------+------+ 
| 77 | 76 | 75 | v = 0x1200  | 66 | 65 | 64 | w = 0x1300 
+------+------+------+      +------+------+------+ 

+------+------+------+      +------+------+------+ 
| 55 | 54 | 53 | x = 0x1400  | 44 | 43 | 42 | y = 0x1500 
+------+------+------+      +------+------+------+ 

+------+------+------+ 
| 33 | 32 | 31 | z = 0x1600 
+------+------+------+ 

+--------+--------+--------+ 
| 0x1000 | 0x1200 | 0x1500 | a = 0x2000 
+--------+--------+--------+ 

+--------+ 
| 0x2000 |      p = 0x3000 
+--------+ 

注意,阵列t .. z位于“任意”位置 - 图中不是连续的。一些数组可能是全局变量,例如来自另一个文件,其他数据可能是同一个文件中的静态变量,但在函数之外,其他则是静态的,但是函数本地的,以及这些本地自动变量。你可以看到p是一个包含地址的变量;地址是数组a的地址。反过来,数组a包含3个地址,即3个其他数组的地址。

这是64位编译程序的输出,人为分割。它通过屏蔽除十六进制地址的最后四位数字之外的所有地址来模拟16位地址。

t   = 0x75DA (size 6) 
u   = 0x75D4 (size 6) 
v   = 0x75CE (size 6) 
w   = 0x75C8 (size 6) 
x   = 0x75C2 (size 6) 
y   = 0x75BC (size 6) 
z   = 0x75B6 (size 6) 

这样可以防止关于未使用的变量的警告,还可以识别7个3个整数数组的地址。

a   = 0x7598 (size 24) 
&a  = 0x7598 (size 24) 
&a[0][0] = 0x75DA (size 2) 
&a[0][1] = 0x75DC (size 2) 
&a[0][2] = 0x75DE (size 2) 
&a[1][0] = 0x75CE (size 2) 
&a[1][1] = 0x75D0 (size 2) 
&a[1][2] = 0x75D2 (size 2) 
&a[2][0] = 0x75BC (size 2) 
&a[2][1] = 0x75BE (size 2) 
&a[2][2] = 0x75C0 (size 2) 
&a[0]  = 0x7598 (size 8) 
&a[1]  = 0x75A0 (size 8) 
&a[2]  = 0x75A8 (size 8) 

    a[0][0] = 99 a[0][1] = 98 a[0][2] = 97 
    a[1][0] = 77 a[1][1] = 76 a[1][2] = 75 
    a[2][0] = 44 a[2][1] = 43 a[2][2] = 42 

请注意重要的区别。 a的大小现在是24个字节,而不是18个,因为它是3个(64位)指针的数组。 &a[n]的大小是8个字节,因为每个都是一个指针。将数据加载到数组位置的方法也大不相同 - 您必须查看汇编器才能看到该数据,因为C源代码看起来相同。

在2D阵列码,为A[i][j]加载操作计算:的A

  • 增加(3 * i + j) * sizeof(short)

    • 字节地址到字节地址
    • 读取从该地址2个字节的整数。

    在指针代码的阵列,为A[i][j]加载操作计算:

    • a
    • 字节地址添加i * sizeof(short *)到字节地址
    • 从该计算出的值取字节地址,称它为b
    • 增加j * sizeof(short)增加到b
    • 取出从地址b

    2字节整数为p输出是有些不同。注意,特别地,地址p与地址p不同。但是,一旦你过去了,行为基本上是一样的。

    p   = 0x7598 (size 8) 
    &p  = 0x7590 (size 8) 
    &p[0][0] = 0x75DA (size 2) 
    &p[0][1] = 0x75DC (size 2) 
    &p[0][2] = 0x75DE (size 2) 
    &p[1][0] = 0x75CE (size 2) 
    &p[1][1] = 0x75D0 (size 2) 
    &p[1][2] = 0x75D2 (size 2) 
    &p[2][0] = 0x75BC (size 2) 
    &p[2][1] = 0x75BE (size 2) 
    &p[2][2] = 0x75C0 (size 2) 
    &p[0]  = 0x7598 (size 8) 
    &p[1]  = 0x75A0 (size 8) 
    &p[2]  = 0x75A8 (size 8) 
    
        p[0][0] = 99 p[0][1] = 98 p[0][2] = 97 
        p[1][0] = 77 p[1][1] = 76 p[1][2] = 75 
        p[2][0] = 44 p[2][1] = 43 p[2][2] = 42 
    

    所有这些都在一个单独的(主要)函数中。当将各种指针传递给函数并访问这些指针后面的数组时,您需要对自己进行并行实验。

  • +0

    谢谢..(虽然我需要一些时间才能完全理解你的阐述)现在这激励了我...... :) – user1171901 2012-02-12 16:58:31

    如果声明一个多维数组:

    int b[M][N]; 
    

    存储是连续的。所以当你访问一个元素时,例如(x = b[i][j];),编译器产生的代码相当于此:

    int *c = (int *)b; // Treat as a 1D array 
    int k = (i*N + j); // Offset into 1D array 
    x = c[k]; 
    

    当通过一个指针到指针访问一个元素,编译器不具有的尺寸的知识,并可以产生这样的代码:

    int *t = b[i]; // Follow first pointer (produces another pointer) 
    x = t[j];  // Follow second pointer 
    

    即它只是遵循指针。

    这些是完全不兼容的,所以编译器会阻止你将一个真正的二维数组传递给一个带指针指针的函数。

    void caller(int b[][3]) // why can't we write **b ? 
    

    你可以写int **b,但你不能传递arr这个功能,因为arr被定义为int arr[3][3]这是与int **类型不兼容。

    arr可以转换成int (*)[3]而不是转换成int **。所以,你可以这样写:

    void caller(int (*b)[3]) //ok 
    

    其实int[3][3]定义阵列的阵列,而int**定义一个指针的指针。 int[3][3]可以转换成指针指向一个3 int(它是int (*)[3]),就像int[3]可以转换成指针int(这是int*)。

    1.更确切地说,它定义了一个3-array-of-3-int的数组。