数组名 & 指针

先来看看如下示例代码:

int arr[10];
int *p1 = arr;
int *p2 = (int*)&arr;
int (*p3)[10] = &arr;
printf("%p %p %p", p1, p2, p3);

输出:

0x7fffaeaff340 0x7fffaeaff340 0x7fffaeaff340

我们知道 数组名可以理解为指向一维数组首元素的指针常量

然而由于数组是一种类型,所以并不能单纯的作为指针常量去判断所有问题

那么&arr是什么?如果arr是指针常量,那么&arr应当是arr的地址

但是 事实上arr与&arr是相等的

这是与指针没有直接关系的,而是数组本身内部的属性

亦即arr存储首元素地址,&arr存储数组地址,并且两个地址相同

基于以上前提,结合我的实验数据,咱们回归本题B选项

int *p1 = arr;

有人认为是指向一维数组的指针

有人认为是指向一维数组首元素的指针

造成这种分歧的原因正是因为两个指针存储的地址值是一样的缘故

而指针是由地址值+数据类型来决定的

所以不能单纯以地址值来谈论,还要基于其指向的数据类型

因为毕竟数据类型决定了指针移动的步长

所以我们写int *p=&arr;这样的语句是编译不通过的

编译器告诉我们:

error: cannot convert 'int (*)[10]' to 'int*' in initialization

那么由此可知,编译器认为int *指向数组元素,而'int (*)[10]'指向数组

这正是类型对指针的决定性影响

所以综上愚以为此处的int *p只能指向一维数组首元素

但是鉴于arr 与 &arr地址值的相等性

所以试验中我使用了强制类型转换 int *p2 = (int*)&arr;

这样编译就通过了

但无法改变int *只能指向单个元素的事实

类似的情况还有函数名

    void foo() {}
    printf("%p %p\n", foo, &foo);

这里打印出

0x100000f40 0x100000f40

与数组名的区别在于

此处foo和&foo是相同的函数指针void(*)()类型

而arr与&arr的指针类型不同

前者指向元素 后者指向数组

至于有的朋友问动态一维数组算不算一维数组

int *p = new int[10];

这个可以查看C11文档关于new表达式的说明

The new expression allocates a memory area, initializes either single object, or an array of objects there and returns a pointer to the first constructed object.

由此可知new关键字创建动态数组时,返回首元素的指针

援引 12.2.1的内容:

Although it is common to refer to memory allocated by new T[] as a “dynamic array,” this usage is somewhat misleading. When we use new to allocate an array, we do not get an object with an array type. Instead, we get a pointer to the element type of the array. Even if we use a type alias to define an array type, new does not allocate an object of array type. In this case, the fact that we’re allocating an array is not even visible; there is no [num]. Even so, new returns a pointer to the element type.

大意如下:

虽然我们常把new分配的T[]对象称为“动态数组”,但是这种说法有点误导人。因为当使用new来分配一个数组,我们并未得到一个数组类型的对象。相反地,我们得到的是一个指向数组元素的指针。即使我们使用一个类型别名来定义某种数组类型,new也不会分配一个数组类型的对象。在这种情况下,你看不出我们是在分配一个数组,因为数组长度[num]压根没有体现出来。所以,new返回的是一个指向数组元素的指针。

D选项事实上是值得商榷的,因为它与上文所引用的C++文档和C++ Primer的论述不符。如果A选项精确表达了数组名的语义(semantics),那么new的语义应该和A选项具有一致性。也就是说,没有什么指针能指向所谓的“动态数组”(dynamic array)。我们new T[num]时,只是分配了sizeof(T)*num大小的堆区内存而已,并没有什么数组类型被创建。返回的只是一个T类型的指针,让你可以方便通过这个指针获得这块堆内存的首地址。以后你想要访问这块地址上的元素,就可以通过这个指针迭代器,以相同的sizeof(T)步长迭代访问这块内存上的元素。

所以,new的返回值和数组名arr一样,视为int *类型

综合上述,D选项错

我的最终结论是 int *p 只能是一个指向int型值的指针

附加福利:

我们用汇编来看看new方法的内部实现

考虑如下示例代码:

    int *p = new int[3]();
    *p = 1;
    *(p+1) = 2;
    *(p+2) = 3;

编译为如下汇编码:

(芯片架构为X86_64,汇编语法格式为AT&T)

# eax寄存器存入12,即我们的动态数组所需空间总大小
movl    $12, %eax
# edi寄存器存入12,作为下面调用的__Znam函数的第一个参数(也是唯一一个)
movl    %eax, %edi
# 系统调用libkern中的__Znam函数,用于申请12字节的堆内存,返回内存首地址到rax寄存器
callq    __Znam
# ecx寄存器清零
xorl    %ecx, %ecx
# edx寄存器存入12,即我们的动态数组所需空间总大小,作为_memset函数的第三个参数
movl    $12, %edx
# 将__Znam函数返回的堆内存首地址存入rdi寄存器,作为_memset函数的第一个参数
movq    %rax, %rdi
# 将ecx寄存器中的值,即0,存入esi寄存器,作为_memset函数的第二个参数
movl    %ecx, %esi
# 由于下面调用memset函数后会使用rax存储返回值,所以需要在栈里保存一下rax中的动态数组首地址
movq    %rax, -16(%rbp)         ## 8-byte Spill
# 保存一下ecx中的动态数组元素默认值
movl    %ecx, -20(%rbp)         ## 4-byte Spill
# 调用memset函数初始化动态数组元素,每个字节清零
callq    _memset
# 动态数组首地址出栈,最终存入rdx寄存器
movq    -16(%rbp), %rax         ## 8-byte Reload
movq    %rax, -8(%rbp)
movq    -8(%rbp), %rdx
# 实现代码中的逻辑,在动态数组第一个元素中存入立即数1
movl    $1, (%rdx)
movq    -8(%rbp), %rdx
# 在动态数组第二个元素中存入立即数2
movl    $2, 4(%rdx)
movq    -8(%rbp), %rdx
# 在动态数组第三个元素中存入立即数3
movl    $3, 8(%rdx)
# 最终返回值寄存器rax中存储的是动态数组内存首地址

其中用到的memset函数原型为

void *memset(void *__b, int __c, size_t __len);

由汇编代码可以清晰地看到new函数的实现过程事实上就是两步:

  1. 调用__Znam函数分配内存空间
  2. 调用_memset函数初始化内存

第二步是可选的,因为示例代码中定义动态数组的语句为

int *p = new int[3]();

所以会有第二步
假如按照如下方式声明则不会有第二步

int *p = new int[3];

一个括号引发的血案,区别在于
前者会使用值初始化
后者使用默认初始化

回到正题
最终函数返回值为__Znam函数返回的动态内存空间首地址
也就是说就是个void *
大家最终对其进行任何强制转换都可以
当然在代码中我们定义为什么类型,返回的就是什么类型
只是大家知道 到了底层其实没所谓类型的
只有数据宽度
而指针这种东西 在X86_64架构就是8字节数据
无所谓类型

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,525评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,203评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,862评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,728评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,743评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,590评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,330评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,244评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,693评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,885评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,001评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,723评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,343评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,919评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,042评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,191评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,955评论 2 355

推荐阅读更多精彩内容