常用的C语法:指针和数组名的区别

在C语言中指针和数组的使用方法几乎是一样的,但他们的确是完全不同的两个东西,如果不能很好的区分,则可能会带来一些问题,想要发现其中的区别,我们则需要从汇编层面观察。

目录

  • 1. 问题场景
  • 2. 汇编分析
  • 3. 总结

1. 问题场景

头文件中的声明对于编译器是一个非常重要的检查手段,但有时候为了方便,我们会直接在C文件中声明外部的符号,这样做编译器将无法判断声明和定义是否一致,可能会带来隐患,非常不推荐这样做。
下面两个文件演示了在一个C文件中定义了一个数组,而在另一个C文件中不小心将它声明成了指针,可以发现该程序编译正常,但运行的时候出现了段错误。

a.c

#include 
volatile uint8_t share_memory[128];

b.c

#include 
extern uint8_t *share_memory;

int main(void)
{
	share_memory[0] = 0xAA;
	return 0;
}

编译运行,编译正常,运行失败

$ gcc a.c b.c && ./a.out
Segmentation fault (core dumped)

避免该错误的方式就是定义一个a.h头文件,在头文件中进行声明,然后两个C文件都包含该头文件。这样做,当声明和定义不一致的时候编译器就会报错。

a.h

#ifndef _A_H_
#define _A_H_

extern uint8_t *share_memory;

#endif /* _A_H_ */

a.c

#include 
#include "a.h"
volatile uint8_t share_memory[128];

b.c

#include 
#include "a.h"

int main(void)
{
	share_memory[0] = 0xAA;
	return 0;
}

编译运行,编译失败

$ gcc a.c b.c && ./a.out
a.c:3: error: conflicting types for 'share_memory'  # 编译器提示类型冲突
a.h:4: note: previous declaration of 'share_memory' was here

2. 汇编分析

我对csky的指令集比较熟悉,下面就采用csky-abiv2-elf-gcc编译的结果来查看指针和数组的区别。
下面这个文件分别定义了指针和数组,又定义了4个函数分别返回指针指向的第一个元素以及数组的第一个成员。

main.c

int *ptr;
int array[1];
int func_ptr1(void)
{
	return *ptr;
}
int func_ptr2(void)
{
	return ptr[0];
}
int func_array1(void)
{
	return *array;
}
int func_array2(void)
{
	return array[0];
}

编译,并反汇编,也可以直接汇编,但汇编的结果不够简洁

$ csky-abiv2-elf-gcc -c main.c -S -O2 -mcpu=ck802 -o main.asm  # 这句话是直接汇编,main.asm中保存了汇编的内容
$ csky-abiv2-elf-gcc -c main.c -O2 -mcpu=ck802 -o main.o && csky-abiv2-elf-objdump -S main.o > main.asm  # 这句话是编译然后反汇编,main.asm中保存了汇编的内容
$ cat main.asm  # 摘录部分内容
...
<func_ptr1>: /* func_ptr2和这个是一样的 */
lrw r3, 0x0 // 8 <func_ptr1+0x8>  /* 将ptr这个指针变量的地址加载到r3寄存器 */
ld.w r3, (r3, 0x0)  /* 将r3寄存器的值作为地址,读取该地址中的数据,放到r3中,即指针的值 */
ld.w r0, (r3, 0x0)  /* 将r3寄存器的值作为地址,读取该地址中的数据,放到r0中,即指针指向的值 */
jmp r15  /* r0存储着返回值,这句话让函数返回 */
...

<func_array1>: /* func_array2和这个是一样的 */
lrw r3, 0x0 // 8 <func_array1+0xc>  /* 将array这个数组的地址加载到r3寄存器 */
ld.w r0, (r3, 0x0)  /* 将r3寄存器的值作为地址,读取该地址中的数据,放到r0中,即数组的第一个值 */
jmp r15  /* r0存储着返回值,这句话让函数返回 */
...

通过上面的汇编发现,指针引用数据会比数组索引数据多一条指令,这也是解引用这个名字的由来。指针本质是一个变量,占有存储空间,汇编需要先访问这个变量,再将变量的值作地址,去访问其指向的值。而数组名本质是一个符号,代表一个常数值(地址值),不占用空间,访问这个地址自然拿到的就是其中的成员。

3. 总结

C语言自由灵活,也存在许多陷阱,有些陷阱需要通过汇编来解释,C语言开发,需要掌握一些基本的汇编知识。

你可能感兴趣的:(c语言,开发语言)