内存指针是什么?为什么指针还要有偏移量?

原文链接: 内存指针是什么?为什么指针还要有偏移量? < Ping通途说

1. 什么是内存指针、偏移量?

内存指针是一个存储内存地址的变量,它指向计算机内存中的某个特定位置。可以把它想象成:

  • 现实世界的比喻:指针就像房子的门牌号,告诉你某个数据"住在"内存的哪个位置
  • 本质:指针本身也是一个变量,但它的值不是普通数据,而是内存地址
cpp 复制代码
int num = 42;      // 普通变量,存储数字42
int *ptr = &num   // 指针变量,存储num的内存地址

而偏移量是让指针真正强大的关键特性,主要可以用于数组访问、结构体和类成员访问、数据遍历,甚至动态操作内存。

以数组访问为例,可以很明显看出其功能,这不就是主流编程语言都有的索引。

cpp 复制代码
int arr[5] = {10, 20, 30, 40, 50};
int *ptr = arr;     // 指向数组首元素

// 通过偏移访问
int third = *(ptr + 2);  // 访问第三个元素(30)

以及令人望而生畏的手动内存管理,实际上在指针上增加偏移值以指向对应内存进行处理:

cpp 复制代码
int *buffer = malloc(100 * sizeof(int));
// 填充第50个位置
*(buffer + 49) = 123;

但偏移量不跟数组的索引一样,而是根据数据类型自动缩放其范围。MySQL中常见的int和tinyint,大家应该都知道一个占4字节,另一个就是占1字节。由于C没有tinyint,所以用char代替

cpp 复制代码
int *ptr = 0x1000;  // 假设内存地址
ptr + 1;            // 实际上指向 0x1004(int通常是4字节)
ptr + 3;            // 实际上指向 0x100C(跳过4x3=12字节)

char *cptr = 0x1000;
cptr + 1;           // 指向 0x1001(char是1字节)

因此需要注意:偏移的不是"第几个值",而是"第几个同类型元素"

2. 再说简单些!以及怎么取一整段数据?

你应该知道,数据最终呈现的格式就是1和0,因此在内存中也都是0/1的位。

指针本身就是一段数据的起点,偏移值就是取起点的第n个同类型元素。

指针偏移就像在内存中"跳格子",每个格子的大小由指针类型决定,你可以精确控制要访问哪个或哪几个连续格子中的数据。

另外需要注意,偏移量的范围不得超出内存范围,否则轻则软件崩溃,重则系统崩溃(蓝屏)。

由于偏移值只能指向一个位置,C也没有Python那样直接通过data[0,5]取范围值,因此主要通过for遍历来操作一段数据:

1. 数组表示法(最简单)

cpp 复制代码
int arr[10] = {0,1,2,3,4,5,6,7,8,9};
int *start = &arr[2];  // 从第3个元素开始
int *end = &arr[7];    // 到第8个元素结束

// 取第3到第8个元素
for(int *p = start; p <= end; p++) {
    printf("%d ", *p);  // 输出: 2 3 4 5 6 7
}

2. 结构体/对象的连续块

cpp 复制代码
struct Point { //定义一个点的结构体
    int x;
    int y;
};

struct Point points[5] = {{1,2}, {3,4}, {5,6}, {7,8}, {9,10}};
struct Point *ptr = points;

// 取连续3个Point
for(int i = 0; i < 3; i++) {
    printf("Point %d: (%d, %d)\n", 
           i, 
           (ptr + i)->x,  // 或 ptr[i].x
           (ptr + i)->y);
}

3. 动态内存操作

cpp 复制代码
// 分配连续内存块
int *buffer = malloc(50 * sizeof(int));

// 方法1: 指针移动
int *current = buffer;
for(int i = 0; i < 50; i++) {
    *current = i * 2;  // 赋值
    current++;         // 指针向后移动一个int
}

// 方法2: 数组下标
for(int i = 0; i < 50; i++) {
    buffer[i] = i * 2;
}

再用一个内存布局示例来展示一下:

假设内存中有这样的int数组:

|--------|----|----------|
| 地址 | 值 | C写法 |
| 0x1000 | 10 | arr[0] |
| 0x1004 | 20 | arr[1] |
| 0x1008 | 30 | arr[2] |
| 0x100C | 40 | arr[3] |
| 0x1010 | 50 | arr[4] |

cpp 复制代码
int *ptr = 0x1008;  // 指向30

// 取连续3个值
int val1 = *(ptr + 0);  // 30
int val2 = *(ptr + 1);  // 40
int val3 = *(ptr + 2);  // 50

实际应用:图像处理示例

cpp 复制代码
// 假设处理1280x720的图像,每个像素4字节(RGBA)
int width = 1280;
int height = 720;
uint8_t *image = malloc(width * height * 4);

// 取第100行开始的连续10行
int start_row = 100;
int row_count = 10;
uint8_t *region_start = image + (start_row * width * 4);
uint8_t *region_end = region_start + (row_count * width * 4);

// 处理这个区域
for(uint8_t *p = region_start; p < region_end; p += 4) {
    // 每个像素4字节: p[0]=R, p[1]=G, p[2]=B, p[3]=A
    p[0] = 255;  // 设置红色
}

3. 越界访问的"界"是什么?

界有两种,一种是系统分配给软件的内存范围,另一种就是数据结构范围。

1. 操作系统层面:进程内存边界

每个程序运行时,操作系统为其分配一个虚拟地址空间(例如:0x00000000到0xFFFFFFFF)

cpp 复制代码
// 试图访问非法地址
int *ptr = (int*)0xFFFFFFFF;  // 可能超出进程空间
*ptr = 42;  // 触发段错误(Segmentation Fault)

// 后果:程序立即崩溃(操作系统保护)
// 保护机制:MMU(内存管理单元)+ 页表

2. 程序层面:数据结构边界

cpp 复制代码
int arr[10];
arr[15] = 42;  // 越过了数组边界但仍在进程空间内

导致的后果:软件可能不会立即崩溃,但会破坏其他变量数据(堆栈破坏),导致程序逻辑错误以及安全漏洞(缓冲区溢出攻击)

相关推荐
冉佳驹2 小时前
C++ ——— 异常处理的核心机制和智能指针管理
c++·异常捕获·异常继承体与多态·重载抛异常·raii思想·智能指针shared_ptr·weak_ptr指针
C++ 老炮儿的技术栈2 小时前
Qt 编写 TcpClient 程序 详细步骤
c语言·开发语言·数据库·c++·qt·算法
yuuki2332332 小时前
【C++】继承
开发语言·c++·windows
梵刹古音2 小时前
【C++】 析构函数
开发语言·c++
wangjialelele2 小时前
Linux下的IO操作以及ext系列文件系统
linux·运维·服务器·c语言·c++·个人开发
非凡ghost2 小时前
PowerDirector安卓版(威力导演安卓版)
android·windows·学习·软件需求
打工哪有不疯的3 小时前
使用 MSYS2 为 Qt (MinGW 32/64位) 完美配置 OpenSSL
c++·qt
代码游侠3 小时前
C语言核心概念复习——C语言基础阶段
linux·开发语言·c++·学习
独行soc3 小时前
2026年渗透测试面试题总结-19(题目+回答)
android·网络·安全·web安全·渗透测试·安全狮