笔记:C (C++)语言中数组与指针的区别

-关于《c专家编程》的读书笔记

—————————————————

试验目的1,比较指针与数组取值方式的异同。2,比较指针与数组作为函数参数时候的异同。

void funcA(int* p)
{
    printf("%d", p[3]);    printf("%d", p[2]);
}

void funcB(int arr[])
{
    printf("%d", arr[3]);    printf("%d", arr[2]);
}

int _tmain(int argc, _TCHAR* argv[])
{
    int arrayInt[5] = {3, 4, 5, 6, 7};
    int* pInt = arrayInt;

    printf("%d", arrayInt[3]);
    printf("%d", pInt[3]);

    funcA(arrayInt);    funcB(arrayInt);

    funcA(pInt);    funcB(pInt);

    return 0;
}

使用vc2008编译,project setting里面选择生成asm代码。

; 23   :     int arrayInt[5] = {3, 4, 5, 6, 7}; 
    mov    DWORD PTR _arrayInt$[ebp], 3
    mov    DWORD PTR _arrayInt$[ebp+4], 4
    mov    DWORD PTR _arrayInt$[ebp+8], 5
    mov    DWORD PTR _arrayInt$[ebp+12], 6
    mov    DWORD PTR _arrayInt$[ebp+16], 7

; 24   :     int* pInt = arrayInt; 
    lea    eax, DWORD PTR _arrayInt$[ebp]
    mov    DWORD PTR _pInt$[ebp], eax

pInt指向arrayInt的首地址_arrayInt$[ebp],将其保存在_pInt$[ebp]

mov    eax, DWORD PTR _arrayInt$[ebp+12]

对于数组的取值arrayString[3],直接访问首地址加3的地址取出数据。在这里,数据存放在eax寄存器。

mov    eax, DWORD PTR _pInt$[ebp]
mov    ecx, DWORD PTR [eax+12]

对于指针取值,要使用两步,首先取得指针的首地址,然后取得首地址加12的地址(也就是eax+12)里包含的数据([eax+12])。拿到的数据存放在ecx寄存器。

根据《C专家编程》里比较的表格,指针是“1,保存数据的地址。2,间接访问数据,首先取得指针的内容,然后把它作为地址提取数据。如果有下标[I],那么指针的内容加上I作为地址,从中提取数据。”

而数组是“1,保存数据。2,直接取得数据,比如a[I]就是以a+I为地址取得数据。”

基本上可以从代码里看出来这两个说明。

而在函数调用里,无论是以数组还是指针作为参数,生成的代码都是这样的:

使用指针作为参数funcA:

; 8    :     printf("%d", p[3]); 
    mov    eax, DWORD PTR _p$[ebp]
    mov    ecx, DWORD PTR [eax+12]

使用数组作为参数funcB:

; 14   :     printf("%d", arr[3]); 
    mov    eax, DWORD PTR _arr$[ebp]
    mov    ecx, DWORD PTR [eax+12]

很显然这两种取数据的方式都是“指针方式”而不是“数组方式”。也就是说,对于指针或者数组作为参数,函数内部都是使用指针方式来使用它们。在这一点上,数组和指针是一样的。

—————————————————

除了这些区别,还有的区别是:

指针通常使用在动态数据长度,而数组的长度一般是固定的(对于c来说是编译时已知的)。

一般来说,数组的分配是在栈上,分配过程是隐含的,而指针要分配空间,要显式使用malloc/free,一般是在堆上(除了alloca)。如果要分配很大的空间(比如使用内存池技术),一般都要使用指针方式,因为程序栈一般都不怎么大。

数组本身是有名字的,定义了以后名字就是指向首地址无法更改。而指针通常指向匿名数据,可以指向不同的地址。

—————————————————

关于字符串,这是一个比较常用而且特殊的例子。

通常的字符串定义方式:char* pStr1 = _T("This is array string");

CONST    SEGMENT
OBNGGKAB DB ‘This is array string’, 00H ; `string’
……
CONST    ENDS

; 23   :     char* pStr1 = _T("This is array string"); 
    mov    DWORD PTR _pStr1$[ebp], OFFSET OBNGGKAB

; 24   :     char arrayString[] = _T("This is array string"); 
    mov    eax, DWORD PTR OBNGGKAB
    mov    DWORD PTR _arrayString$[ebp], eax
    mov    ecx, DWORD PTR OBNGGKAB+4
    mov    DWORD PTR _arrayString$[ebp+4], ecx
    mov    edx, DWORD PTR OBNGGKAB+8
    mov    DWORD PTR _arrayString$[ebp+8], edx
    mov    eax, DWORD PTR OBNGGKAB+12
    mov    DWORD PTR _arrayString$[ebp+12], eax
    mov    ecx, DWORD PTR OBNGGKAB+16
    mov    DWORD PTR _arrayString$[ebp+16], ecx
    mov    dl, BYTE PTR OBNGGKAB+20
    mov    BYTE PTR _arrayString$[ebp+20], dl

可以看出对于char* pStr1 = _T("This is array string"); 来说,它是指向了静态只读数据段的OFFSET。基本上所有靠谱的C语言编程书都会提到,不要修改这种方式声明的字符串内容。

pStr1[1] = ‘H’; // critical error

如果这样的代码运行起来,会导致下面的错误。

Unhandled exception at 0×00411594 in testbbbb1.exe: 0xC0000005: Access violation writing location 0×004157c1.

而以数组方式声明的字符串(char arrayString[] = _T("This is array string"); ),虽然也使用了静态只读数据段里内容,但很显然是复制而不仅仅是指向偏移,所以修改其中的内容不会导致错误。

笔记:c语言中的复杂声明

最近在复习c(c++)语言里面的复杂声明,这个东西真是麻烦的紧啊。

例子1,来自http://msdn.microsoft.com/en-us/library/1×82y1z4(VS.80).aspx

char *( *(*var)() )[10];
var是一个指针,指向一个函数,函数参数为空,函数返回一个指针,指向一个数组(10个元素),数组里面保存char*。
 

unsigned int *(* const *name[5][10] ) ( void );

name是一个2维数组(5×10),保存的是指向const型函数指针的指针,函数指针返回值为UINT指针。

The name array has 50 elements organized in a multidimensional array. The elements are pointers to a pointer that is a constant. This constant pointer points to a function that has no parameters and returns a pointer to an unsigned type.

union sign *(*var[5])[5];

var声明了一个指针数组,指针指向一个union sign指针的数组(5个元素)。

Array of pointers to arrays of pointers to unions.

 

例子2,来自http://www.codeproject.com/KB/cpp/complex_declarations.aspx

const char * const * const p8;

p8是一个const指针,指向一个const指针pa,pa指针指向const char类型。

const pointer to const pointer to const char

int * (* (*fp1) (int) ) [10];

fp1是一个函数指针,参数为int,返回值为一个指针pb,pb指向一个int指针的数组。

fp1 is a pointer to a function that takes an int as argument, and returns a pointer to an array of 10 pointers to ints.

 

例子3,来自《C programming language》,123页的5.12 Complicated declarations

char (* (*x[3]) () ) [5]

x是一个数组,包含3个函数指针,函数指针返回一个指向char[5]的指针。

x: array[3] of pointer to function returning pointer to array[5] of char.

void (*signal(int sig, void (*handler)(int)))(int) 来自255页的标准库说明,这个是我看到的现实世界里用到的复杂实例。(不好意思读的代码太少了,见识不广)

这个signal函数也经常写作如下格式(from dietlibc):

typedef void (*sighandler_t)(int);

sighandler_t signal(int signum, sighandler_t action);

这样看就容易多了。

 

例子4,来自《C专家编程》3.4

char *(* c[10])(int **)

c是一个函数指针数组,参数为int**(指向int指针的指针),返回值为char*。

 

关于如何解析复杂的声明,可以参考前面提到的相关文档。其中C专家编程里面提到了一个人机皆可使用的规则。

A 声明从名字开始读取,然后按照优先级依次读取。

B 优先级从高到低依次为:

B1 被括号括起来的部分

B2 声明后缀符, 括号表示是一个函数,方括号【】表示是一个数组。

B3 前缀操作,*表示“指向…的指针”

 

C 如果const和(或)volatile关键字后面紧跟类型说明符(如int long double),那么它作用于类型说明符。在其他情况下const volatile关键字作用于它左边紧邻的指针星号。

unix世界有个著名的程序叫做cdecl,专门负责解析C语言声明,在c programming language里面有个简易版说明了其工作原理。

时光

看时光飞逝,我回首从前。。。

IMG_1217

IMG_1756

IMG_5629

IMG_6131

IMG_6500

IMG_6685

Picture 055

IMG_7292

IMG_7007

Python Introduce

准备在公司内部的reading group做的演示。

照片09年6月12日

上星期去劳动公园照的,奥林巴斯mju2,富士400卷店扫。

———————

神气二人组

17510002

自我陶醉

17510005

漂亮的小黄花。

17510011

最喜欢的游乐项目之一。

17510013

喷水枪的。

17510014

木马,等待开动。

17510015  

17510016

很臭美。

17510017

刚进园子时候拍的。

17510019

家里,这个风格有些像老照片。

17510030

←Older