添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

warning: the address of ‘a’ will always evaluate as ‘true’ [-Waddress]

自然,判断一个数组是否为真是多余的,因为它确定无疑是真的!另一方面,把判断条件换为

if (a != NULL)

时,是不会产生编译警告的。就是这样一个在语法上没有问题的小程序,引起了我的兴趣。我想就此一探指针与数组之间的微妙关系。

二、数组不是指针

1. 先上定义

数组,用来存储一个固定大小的相同类型元素的顺序集合。在c语言中,数组属于构造数据类型。

有限个类型相同的变量的集合命名为数组名。组成数组的各个变量称为数组的分量,也称为数组的元素或下标变量。用于区分数组的各个元素的数字编号称为下标。

数组是用于储存多个相同类型数据的集合。

指针是一种保存变量地址的变量。

指针(Pointer)的值直接指向(points to)存在存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元。

2. 定义与声明的区分

经常会遇到下面一段程序代码的情况:

/* 文件 1 */
int a[10];
/* 文件 2 */
extern int *a;
/* 下面是开始使用a[i]的代码 */

很多人认为这是没有问题的,毕竟,在c语言中数组与指针非常相似,甚至可以互换。那么,把定义为数组的变量声明为指针使用有什么问题呢?

对于下面的这段代码,没有人怀疑它是错误的:

/* 文件 1 */
int i;
/* 文件 2 */
extern float i;
/* 下面是开始使用i的代码 */

对于定义与声明类型不匹配的情况,没人指望它能正常运行。但是数组与指针的类型也不相同啊,为什么还要指望它能正常运行呢?

c语言中的变量必须有且只有一个定义,但它可以有多个 extern 声明,定义创建了一个对象,并为其分配了内存空间。而声明只是简单地说明了在其他地方创建的对象的名字,它允许你使用这个名字而已。

编译器为每个变量分配一个地址,地址编译时可知,并且该变量在运行时一直保存在这个地址。相反,存储在变量中的值只有在运行时才可知。当需要从变量中存储的值时,编译器从指定地址读出变量的值在于寄存器。

对于数组,编译器可以直接对其操作,不需要增加指令首先取得具体的地址。对于指针,必须首先在运行时取得它的当前值,然后才能对它进行解除引用操作。

所以, extern char a[] extern char a[10] 等价。编译器并不需要知道数组具体长度,它只产生偏离起始地址的偏移地址。从数组中取一个字符,只要简单地从符号表显示的a的地址加上下标,需要的字符就在这个地址中。

而声明为 extern char *p ,编译器认为p是一个指针,它指向的对象是一个字符,为了取到这个字符,要先取得地址p的内容,然后把它作为字符的地址,从而取出字符。

可见,指针的访问比数组增加一次额外的提取。

同样的道理,下面的使用会导致什么后果呢?

/* 文件 1 */
int *a;
/* 文件 2 */
extern int a[];
/* 下面是开始使用a的代码 */

外部数组实际定义为指针,但作为数组使用,需要对内存进行直接的引用,但这里编译器所执行的却是对内存进行间接引用。因为编译器认为它是一个指针,而非数组。

再回过头来看一下这个问题,p声明为 extern char *p ,而它原本的定义是’char p[10]’时,当用p[i]使用p时,实际上得到的是一个字符,但编译器会把它当成一个指针。把ASCII码解释成地址显然很荒谬,这时程序崩溃你应该感到很高兴,不然,这个bug可能会破坏程序地址空间的内容,出现莫名其妙的错误。

3. 数组和指针的特性对比表
数组 指针
保存数据 保存数据的地址
直接访问数据 间接访问数据,先取出指针的内容,把它作为地址,然后从该地址取出数据
通常用于存储固定数目且类型相同的元素 通常用于动态数据结构
隐式分配和删除 使用malloc()和free()
自身为数据名 通常指向匿名数据
4. 字符串常量

指针和数组的另一个区别是定义字符串常量时。使用指针定义一个字符串常量,如:

char *p = “abcdefg”;

此时,编译器会为字符串常量分配内存。注意,只有字符串常量才是如此,不会为浮点数之类的常量分配空间。在ANSI C中,初始化指针所创建的字符串常量被定义为只读,如果试图通过指针修改这个字符串常量的值,就会出现未定义的错误。

使用数组定义字符串常量时,如:

char a[] = “abcdefg”;

字符串的值是可以修改的。

三、数组是指针

1. 数组是指针的情况

其实,在实际的应用中,数组和指针可以等同的情况要比它们不能互换的场景多得多。例如,牢记以下准则:

所有作为函数参数的数组名总是可以通过编译器转换为指针。

而在任何其他情况下,数组的声明就是数组,指针的声明就是指针,两者不可互换。但在使用数组时,数组总是可以写成指针的形式,两者可以互换。

数组与指针相同的情况总结如下:
- 表达式中的数组名被编译器当作一个指向该数组第一个元素的指针
- 下标总是与指针的偏移量相同
- 在函数参数的声明中,数组名被编译器当作指向该数组第一个元素的指针

2. 为什么把数组当成指针

基于以下原因,c语言把数组形参当作指针使用:
- 把传递给函数的数组参数转换为指针是由于效率的考虑
- 把作为形参的数组和指针等同起来也是出于效率的原因。c语言中,所有非数组形式的实参均传值,但如果要拷贝整个数组,开销太大,而且绝大多数情况下,也不需要。

3. 数组可以当作指针的情况总结
  1. a[i] 这样的形式对数组访问总是被编译器解释成按 *(a+i) 的指针访问
  2. 指针始终都是指针,它不会改写成数组。但可以用下标形式访问指针。
  3. 在作为函数的参数时,一个数组的声明可以看作是指针。作为函数参数的数组始终会被编译器修改成为指向数组第一个元素的指针。
  4. 当把一个数组定义为函数参数时,可以把它定义为数组,也可以定义为指针。不管选择哪种方式,在函数内部事实上获得的都是一个指针。
  5. 在其他所有情况下,定义和声明必须一致。

四、总结一下

理解了数组和指针何时可以互换何时必须各自使用之后,对于下面语句的含义应该很清楚了:

/* 数组名和指针都可以作为函数的参数使用 */
char a[10];
char *p;
i = strlen(a);
j = strlen(p);
/* 这个语句清晰地展示了数组和指针的可互换性 */
printf("%s %s", a, p);
/* 下面这行语句要仔细理解其原理哦 */
printf("array at location 0x%x holds string %s", a, a);
/* 下面两行可互换的程序也超常见 */
int main(int argv, char *argv[]);
int main(int argv, char **argv);

今后,程序出现错误时认真分析,避免因误用导致的程序错误,遇到指针和数组混合使用的情况时多多总结,相信一定会有更多收获和进步!

参考资料:
《c专家编程》

f (1, 2, 3, &i, &i); warning: the address of ‘i’ will always evaluate as 'true' [-Waddress] -Warray-bounds: 数组 越界告警,如 数组 下标访问越界。 C语言 数组 指针 一直是两个容易混淆的东西,当初在学习的时候,也许为了通过考试会对 指针 数组 的一些考点进行突击,但是很多极其细节的东西也许并不是那么清楚。本篇侧重点在于 分析 数组 指针 的关系,什么时候 数组 可以等同于 指针 ,什么时候二者不同。 数组 的访问 首先通过一张图来描述一下 数组 的访问过程,下图的字符 数组 ,访问下标为9的的元素 文章目录1. 不同之处1. 数组 名的指向不可以改变,指向 数组 指针 可以改变2.字符串的2种初始化方式3.组名可求得 数组 长度,而 指针 却得不到 数组 长度2. 相同之处1.使用索引的时候2. 数组 作为 函数参数 的时候 1. 不同之处 数组 名不可以改变,而指向 数组 指针 是可以改变的。 指针 指向字符串,此时的字符串是不能改变的;而字符 数组 定义的字符串,字符是可以改变的。 求 数组 长度时,借用 数组 名可求得 数组 长度,而借用 指针 却得不到 数组 长度。 自己定义了一个函数: bool phead_is_merge_into_head(const IR1_INST *const pir1, FLAG_PATTERN_ITEM *item, FLAG_PATTERN_PHEAD* phead_for_tail) { CONDITION c = pir1->get_cond(); uint8 phead_number = phea 本文由公众号【嵌入式ARM】整理1. 指针 数组 定义、 区别 1.1 指针 数组 定义 指针 数组 都是 C语言 的精髓所在,对于很多C程序员来说,如果你问这样一个问题: 数组 指针 有什么 区别 ?他们的答案很可能是:” 数组 指针 不是同一样东西吗,他们之间有什么 区别 啊?“;确实在极个别的情况下, 数组 指针 确实可以”通用“。但是在却大多数情况下, 数组 指针 是两个完全不同的 C语言 特性。首先,我们分别看一下, 指针 和数... 数组 名 arr1 一般表示的是首元素的地址,以下两种情况除外: (1)&arr1表示整个 数组 元素的地址; (2)sizeof(arr),其 arr表示的是整个 数组 ,sizeof(arr)计算的是整个 数组 的大小,单位是字节; arr和&arr的辨析,请下面图解: 1.2.二维 数组 指针 :一个变量的地址称为该变量的“ 指针 ”。 区别 :1.把 数组 作为参数传递时,会退化为 指针 数组 名作为函数形参时,在函数体内,仅仅只是一个 指针 ;在失去其内涵的同时,还失去了其常量特性,可以做自增、自减等操作,可以被修改。 2. 数组 名可以转换为指向其指代实体的 指针 ,但是它只能被看作一个 指针 常量,不能被修改。而 指针 不管是指向结构体、 数组 指针 数组 的不同1. 指针 数组 保存的数据内容不同 指针 保存 指针 变量,即保存数据的地址; 数组 保存同一类型数据的值2. 指针 用于动态数据结构,就是说 指针 保存的数据可以改变;而 数组 用于存储固定数目且数据类型相同的元素即一旦 数组 的大小定了就不容易变了;3. 指针 用malloc()和free()来分配空间和释放空间,而 数组 是隐式分配和删除;4. 指针 通常指向匿名数据,而 数组 自身就是 数组 名 5. 数组 名作为...