资讯动态

从零开始的逆向之旅~数组与指针(地址)篇

发布时间:2026/9/16 19:12:11 来源:尧图企业网站定制
如果无特殊提醒我将会默认读者熟悉C语言或C的基础语法还请注意一下。另:本文及之后对代码的讨论只限于c编译方式工具链使用minGw与cmake还请注意。数组——C语言或是C的一种存储集中型数据的方案简单来说数组是存储数据的最基础的方式。作为最简单的数据存储方式之一设计者并没有对数组进行高级的封装包括指针在内二者几乎是程序员与内存交互的唯一途径。他们很简单但是有点过于简单了导致使用的时候相当的蹩脚——不过没有关系俗话说万事开头难。首先看数组的定义int a[10];我们知道这个语句简单的定义了一个名为a的数组长度为10。为了进行更深的理解我们可以对这个式子进行简单的拆解noptr-declarator[expr (可选)]attr (可选)其中noptr-declarator可以是任何有效的声明符例如上面代码中的int整形数组或者float浮点数组再或者是char字符数组之类。expr可以是任何的常量表达式在C14之前或者是类型为std::size_t的转换常量表达式C14之后attr为C11开始使用的特性引入用于类型、对象、代码等的实现定义属性。这不是我们今天讨论的范围。我们回到expr上面上面说过这是个常量表达式或是一个类型为std::size_t的转换常量表达式——我们可以换个说法实际上常量表达式就是可以在编译时求值的表达式更简单的说在C中形如int n; cin n; int arr[n];这样的代码是不被C标准所明确允许的如此声明变长数组尽管很多编译器对其进行了适配仍属于非标准行为C中应该用new/delete来声明边长数组。我们用T来形容声明符形如T a[N]的数组该对象由N个T类型的对象组成分别为a[0],a[1]......a[N-1]。数组可以为任何的基本数据类型当然除了void来提供载体除了基本类型其他包括类、指针的对象只要是完整的都可以使用数组进行存储。我们经常使用 a[n]来访问一个数组其中[]被称为成员访问运算符在程序中等价于*(a n)。什么意思呢a在这里被声明为数组对象在运算中它经常被隐式转换为指针类型指向这个数组的起始位置所以上式可以被解读为取a数组起始位置之后第n个元素。为什么可以这么取因为程序在给数组分配空间时经常分配一块连续的区域给数组使用知道了数组在内存中是连续的我们就可以有如下的操作方式int main() { int a[4] {1, 2, 3, 4}; int* p a[2]; std::cout p[1] p[-1] 1[p] (-1)[p] \n; }输出为4242我们来逐行解析一下。int a[4] {1, 2, 3, 4};声明了一个元素为1,2,3,4的数组很简单。int* p a[2];声明了一个名为p的指针变量指向了数组a的第三个元素的地址数组是从0起的a[0]的元素就是1。std::cout p[1] p[-1] 1[p] (-1)[p] \n;接下来是最重要的输出环节我们刚说过p是指向数组a第三个元素地址的指针变量而p[1]等价于*p 1我们不妨简单做一下运算既然int* p a[2]我们进行换算有p a 2其中a被隐式转换称指针类型变量所以p[1]就是*(p 1)*(a12)*(a3)a[3]也就是访问a的第4个元素输出的就是4同样的如果是p[-1]我们很容易得出其等价于a[1]也就是a的第二个元素输出的是2。理解了数组的本质后面的1[p]也很好理解了因为加法有交换律所以1[p] *(1 p) *(p 1) p[1]。所以最后输出4242就很好理解了因为p[1]和1[p]并没有任何区别除了会让代码变得很难看。好了接下来我们来看指针。指针的声明方式有两种一种是指针声明符一种是指针声明符另一种是指向成员的指针声明符其中后者不在我们今天的讨论范围内我们只讨论前者就好。指针声明符的定义如下*attr (可选) cv (可选) declarator我们不去深究这定义的格式考虑形如S* D的声明其中D被声明为指向S类型的指针S类型可以是任意声明说明符包括但不限于intchar或是string需加string文件头等类也可以。所有的指针都可以是如下四个类型之一指向对象或函数的指针在这种情况下该指针被称为指向该对象或函数或指向对象末尾之后的指针或该类型的空指针值或无效指针值。当然这四种类型中每一个都值得单开一个文章进行介绍我们今天只讨论指向对象的指针就好。虽然有些晚我们先介绍一下跟指针相关的两种运算符和*。在C中被称为是取地址符是成员访问运算符之一它的作用就是对紧跟其后面的成员我们在本文章仅讨论对象实际上对函数等也通用取地址形如int a; int* b a;其中b的值就是a的地址b被称为指向a的指针。再往深一点进行探讨何为地址在现代计算机系统中一个程序想要运行就必须将其导入进内存中而所谓地址就是程序在内存中的位置。操作系统通过地址来寻找到各变量与函数的内容以供程序使用地址是计算机中非常底层且重要的知识我们之后会详细解释地址的深层知识及其底层实现与运用。理解了地址我们就可以理解指针我们常说地址≈指针实际上指针存储的是指向内容的地址当我们讨论一个指针的时候通常将指针视为两种内容的集合。其中之一便是地址这是我们刚才就说过的。另一种就是指针所指向内容的类型。什么意思呢考虑以下代码int a 1; char* p1 a; int* p2 a;可以看到编译器给我们报错——不能用int* 类变量来初始化char*变量我们对它进行强制类型转换变成如下代码int a 64; //ASCII码表中的“A” char* p1 (char*)a; int* p2 a;同时输出p1p2我们可以看到这两个指针变量指向了同一个值但是编译器却阻止我们把两个值进行比较报错为“不能将运用于int*与char*”。因为指针变量除了存储地址它还存储了其指向对象的类型所以编译器阻止了我们对其的比较。接下来我们讨论一下解引用符*。有了前面的知识储备我们很容易知道形如cout *p2 endl;这一行代码意味着什么那么cout *p1 endl;这段呢我们知道*是对地址的解引用这是什么意思地址是指引计算机找到对应值的“地图”指针还存储着指向变量的类型这在上面的cout里面是什么意思很简单——既然地址是地图那么指针存储的数据类型就是找到的“宝藏”的用法。用更通俗的话来讲操作系统依照代码的请求找到了处于某个地址的一个变量值而指针的存储类型就决定着操作系统该把依照地址找到的这一堆数字当成什么。现在按照上面的cout输出一遍可以看到两个值——分别是Ap2和65p1所以我们知道了在c系这类强类型语言中指针变量具有类型并且不允许互相换算的原因了——操作系统需要以指针的类型为依据解读沿地址寻找到的值。注本文的C规范参照以及部分代码来自https://cppreference.cn/

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价