嵌入式学习之c语言指针

一、指针基础

1.概念
  • 计算机中的内存中的每一个字节都有一个地址,地址用16进制表示。一般可以理解为指针就是地址,地址就是指针。
  • 定义指针
int* ptr;//定义了一个指向整数的指针,ptr中可以存储int类型的首个字节的地址,int就是让它连续读sizeof(int)个字节。
--------------------------------------------
int a =10;
int* ptr = &a;//ptr现在存储的是a的地址,是int*类型
----------------------------------------------
int value = *ptr //ptr解引用,也就是降级,故value被赋值为10*”是间接访问操作符,作用是通过指针访问存储在该地址上的值
  • 指针是一个变量,它存储了另一个变量的内存地址。换句话说,指针本身存储的是一个内存地址,而不是一个具体的值。
  • 指针有类型,这意味着它指向的是特定类型的数据。例如,int* 是一个指向整数的指针,char* 是一个指向字符的指针,double* 是一个指向双精度浮点数的指针。
2.野指针
  • 野指针(dangling pointer)是指向已释放或未分配内存区域的指针。它们通常是在以下情况下产生的:
    1.指针没有初始化:指针变量被声明但未初始化,指向一个不确定的位置。
#include <stdio.h>

int main() {
    int* ptr;  // 未初始化的指针,指向不确定的位置
    *ptr = 10; // 未定义行为,可能会导致程序崩溃

    return 0;
}

2.指针指向的内存被释放:指针指向的内存被释放后,指针未被置为 NULL 或未重新指向有效内存。

#include <stdio.h>
#include <stdlib.h>

int main() {
    int* ptr = (int*)malloc(sizeof(int)); // 动态分配内存
    *ptr = 10;
    free(ptr); // 释放内存
    *ptr = 20; // 未定义行为,ptr 成为野指针

    return 0;
}

3.超出作用域:局部指针变量指向的内存超出其作用域,导致指针悬挂

#include <stdio.h>

int* danglingPointer() {
    int localVar = 10;
    return &localVar;  // 返回局部变量的地址,局部变量在函数返回后被销毁
}

int main() {
    int* ptr = danglingPointer();
    printf("%d\n", *ptr); // 未定义行为,ptr 是一个野指针

    return 0;
}
  • 避免野指针的方法
    1.初始化指针:声明指针时将其初始化为 NULL。
    2.释放内存后将指针置为 NULL:释放动态内存后,将指针置为 NULL。
    3.慎用局部指针返回值:不要返回指向局部变量的指针。可以使用动态内存分配或者静态变量解决此问题。
    4.定期检查指针有效性:在使用指针前,检查其是否为 NULL。
if (ptr != NULL) {
    // 安全使用指针
}
指针变量

1.存储指针的变量称之为指针变量,即指针变量空间中存储的是地址。
2.指针变量中存储的都是其他变量的首地址。
3.存储一级指针地址的指针变量,称之为二级指针。依次类推,就是多级指针

int*** ptr3 ---->ptr3中可以存储int**类型变量空间的首地址,ptr3自己则是int ***类型

4.指针变量的大小与指针变量类型没有关系,而是与操作系统有关系,32位操作系统地址空间最大为2^32,即指针变量为4个字节。64位操作系统依此类推为8个字节。
5.&取变量首地址,但无法获取常量的地址,打印地址则用%p
6.指针变量定义时可以给一个具体的指针,如果现在没有具体的值,那么将该指针指向null,占位。

int* pe;
pe = &e;//等价于int* pe=&e;
---------------------------------------------------------
int* pc=&b;//将b变量的首地址,赋值给pc,此时可以说pc指向b
int* pd=pc;//将pc空间的数据赋值给pd,那么可以说pd和pc指向同一个内存空间

7.在口头表述上,我们将取地址称之为升级,解引用称之为降级
取一次地址,类型会多一个*,例如int a升级后会成为int*类型。 int* ptr升级后会变成int**类型
解引用一次,类型会少一个*,例如int** ptr降级后会成为 int*类型。int* ptr降级后会变成int类型。

指针数组

存储指针的数组,称之为指针数组

int* brr[4];    ==>数组的容量为4为,其中的每一个元素都是int*类型  

在这里插入图片描述

数组指针

1.数组指针 :这是一个指针,指向整个数组。
3.把整个数组当成一个整体(当成一个数据),数组指针就存储的这个数据的首地址。
2.定义

int arr[5] = {1, 2, 3, 4, 5};
int (*p)[5] = &arr;  // p 是一个指向包含 5 个 int 的数组的指针

内存地址:arr 和 &arr 指向相同的内存地址,即数组的起始位置。
类型:arr 的类型是 int (*)[4],而 &arr 的类型是 int (*)[3][4]。
用途:它们在某些上下文中可以互换使用,但要注意类型的差异。例如,作为函数参数时,通常会使用 int (*)[4] 而不是 int (*)[3][4]。
4.取整个数组的首地址:&数组名,但是用printf打印(%p打印地址值)是一样的,但是他们的数据类型不一样

arr是int*类型
p则是 int (*p)[5]类型

5.arr+1是偏移一个元素,p+1则是偏移一个数组。
6.*p 的类型是 int*,即指向数组首元素的指针。在这里,*p 等价于 arr 或 &arr[0]
6.*p代表将p数组指针降级(解引用)为指向数组首元素的指针。

d=(*p)[0]; //将数组arr[0]的值赋值给d,即*p等价于arr,故为一级指针,也可以写成d=*((*p)+0)

7.范例

#include <stdio.h>

int main() {
    int arr[5] = {1, 2, 3, 4, 5};
    int (*p)[5] = &arr;  // 定义一个指向数组 arr 的指针

    // 使用 *p 访问数组元素
    for (int i = 0; i < 5; i++) {
        printf("(*p)[%d] = %d\n", i, (*p)[i]);
    }

    // 将 *p 赋值给 int* 指针
    int* ptr = *p;
    for (int i = 0; i < 5; i++) {
        printf("ptr[%d] = %d\n", i, ptr[i]);
    }

    return 0;
}

8.int* ptr = *p; 将 *p 赋值给一个指向整数的指针 ptr,使得 ptr 可以像普通指针那样遍历数组元素。

指针与二维数组

1.可以理解为数组的数组
2.二维数组在内存中是以连续的块存储的,也就是说,所有的元素按行优先(row-major order)顺序存储。即二维数组是连续的
3.二维数组用指针访问

#include<stdio.h>
int main(){

int arr[3][4] = {
    {0, 1, 2, 3},
    {4, 5, 6, 7},
    {8, 9, 10, 11}
};
int (*p)[4] = arr; // p是一个指向包含4个 int 的数组的指针
for(int i=0; i<3; i++)
{
for(int j=0; j<4;j++) // 输出四种不同方式访问二维数组元素的结果
printf("%2d %2d %2d %2d\t",*(*(p+i)+j),(*(p+i))[j],*(p[i]+j),p[i][j]);
putchar('\n');
	}
}
----------------------------------------------
*(*(p+i)+j)---------->先对p做偏移,即移动以一维数组的大小为单位,然后再解引用,此时*(p+i)为第i行首元素地址,再在第i行内偏移j个字节,再解引用一次,此时效果相当于p[i][j]
(*(p+i))[j]---------->[]也可以降级,即解引用,但是一定要注意的是*(p+i)一定要用圆括号括起来,避免步骤错误。[]一定要是一维数组首元素地址
*(p[i]+j)------------>p[i]实际上等同于*(p+i),即第i行首元素地址。
在表达式中,arr 可以隐式转换为 int (*)[4],即指向包含 4int 元素的一维数组的指针。
p 不是一个二维指针,而是一个指向一维数组的指针。具体来说,p 是一个指向包含 4int 元素的数组的指针,其类型是 int (*)[4]
  1. 列指针与行指针
    行指针本质上就是一个数组指针,能够指向一整行。
    对数组指针进行降级后,会指向数组的首元素。
    二维数组的名字就是二维数组的首行地址
    所以对行指针进行降级后,会指向数组内的首元素。将行指针降级后的指针称之为列指针。
    4.区别
int *q[5];//此时是指针数组,等价于int *(q[5])
int (*q)[5];//此时为数组指针
arr 和 &arr[0][0] 在内存地址上可能相同,但它们的类型和用途不同。
arr 是指向二维数组行的指针,而 &arr[0][0] 是指向第一个元素的指针。
&arr[0][0] 作为指向第一个元素的指针,可以按一维数组的方式线性访问整个数组的所有元素。

指针与函数

指针的传值和传指
传值
  • 将变量空间中存储的数据传入到函数,对传递前后对实参不会产生影响
  • 传入的值会赋给临时的局部变量,函数内部是对这个临时的局部变量进行操作
传指
  • 将变量的首地址传入函数,函数内部可以通过该地址,访问到外部的存储空间,因此就可以改变函数外部实参中的数据
数组的传递
  • 数组传递的时候,无法将整个数组传入函数。而是将数组的首元素地址传入函数。是个传指的过程
void  func1(int* parr)  //int* parr:需要传入一个int*类型数据   
void  func1(int arr[]) //int arr[]实际上是一个int类型指针:int*
void func3(int arr[200])   //int arr[200]:在形参位置代表int*类型指针。
  • %s在printf中打印时,会遇到’\0’停止

  • int arr[]在形参位置上本质是一个int* 指针,另外可以提醒用户传入数组首地址。

main函数的参数
#include<stdio.h>
int main(int argc,const char* argv[])
-------------------------------------------
argc的值是位置变量的个数,包含了./a.out
argv指向的是一个指针数组,数组中每个元素的都是char*类型,char*类型又指向着以字符串形式存储的,位置变量的内容。
  • "345"作为右值赋值给非字符串数组时是相当于给了一个地址
char *ptr = "hello";
//会将字符串常量 "hello" 的地址赋给字符指针 ptr
指针函数
  • 返回值是指针类型的函数,本质上是一个函数
    在这里插入图片描述
函数指针
  • 指向函数的指针。本质上是一个指针,它允许你动态地调用不同的函数。
int (*p)(int,int);//p可以指向一个函数,该函数返回值是必须是int类型,参数列表必须是(int,int)类型
  • 函数的名字就是函数的首地址,这和数组很像。
#include <stdio.h>

// 定义一个函数,返回值为整数,接受两个整数参数
int add(int x, int y) {
    return x + y;
}

// 定义一个函数,返回值为字符指针,接受两个字符指针参数
char *concat(char *str1, char *str2) {
    // 在堆上分配内存来存储合并后的字符串
    char *result = malloc(strlen(str1) + strlen(str2) + 1); // +1 是为了存储字符串结束符 '\0'
    
    // 检查内存分配是否成功
    if (result == NULL) {
        fprintf(stderr, "Memory allocation failed\n");
        exit(1);
    }
    
    // 将两个字符串拼接到一起
    strcpy(result, str1);
    strcat(result, str2);
    
    return result;
}

int main() {
    int a = 5, b = 3;
    int *p_int = &a; // p_int 指针指向整型变量 a 的地址
    
    int (*p_array)[4]; // p_array 是一个指向包含4个整数的数组的指针

    int (*p_func)(int, int); // p_func 是一个指向接受两个整数参数并返回整数的函数的指针
    char *(*p_strfunc)(char *, char *); // p_strfunc 是一个指向接受两个字符指针参数并返回字符指针的函数的指针

    // 使用指针 p_int 来访问变量 a 的值
    printf("Value of variable a: %d\n", *p_int); // 输出: 5
    
    // 将 p_array 指向一个包含 4 个整数的数组
    int arr[4] = {1, 2, 3, 4};
    p_array = &arr;

    // 使用指针 p_array 访问数组的元素
    printf("Value of first element in array: %d\n", (*p_array)[0]); // 输出: 1

    // 将 p_func 指向函数 add
    p_func = add;
    // 调用函数指针 p_func 所指向的函数
    int sum = (*p_func)(a, b); // 等价于 sum = add(a, b)
    printf("Sum of a and b: %d\n", sum); // 输出: 8

    // 将 p_strfunc 指向函数 concat
    p_strfunc = concat;
    // 调用函数指针 p_strfunc 所指向的函数
    char *result = (*p_strfunc)("Hello, ", "world!"); // 等价于 result = concat("Hello, ", "world!")
    printf("Concatenated string: %s\n", result); // 输出: Concatenated string: Hello, world!

    // 释放动态分配的内存
    free(result);

    return 0;
}

回调函数
  • 将函数A的地址作为参数传入另外一个函数B中,该函数A被称之为回调函数。此时B中有A的地址,所以在B中可以调用函数A。
指针常量和常量指针
  • const修饰的数据类型会变为常类型,用常类型定义的变量,其中存储的数据不允许被修改,只读的。让变量常量化。
  • const修饰普通变量
const int b=10;
int const b=10;//两者等价,b均不允许被修改
指针常量
  • 指针是一个常量,这表明指针空间中存储的地址值不允许被修改。但是指针指向的那块空间中的内容可以修改
int* const ptr;
常量指针

从Linux虚拟机粘贴到Windows中

1.先同时按下ctrl + shift,然后拖动鼠标选择要复制的内容,选择之后再按下c(注意这个过程ctrl +shift在按下c的过程中不要松)
2.在Windows中直接按下ctrl+v即可

也可以通过共享文件夹的方式
Linux中共享文件夹的位置是

/mnt/hgfs/共享文件夹

cp 复制的文件(在该目录下) /mnt/hgfs/共享文件夹/可以重命名

在Linux中找文件夹,点其他位置,再点击计算机,此时就可以查看所有文件了

tips

void GetMemory(char *p)//函数错误,传入p(p实际上是地址,类型为char *型)
{
	p=(char*)malloc(100);
}
void Test(void)
{
	char *str=NULL;
	GetMemory(str);//此时相当于p=str;p=char(char *)malloc(100);因此str没有作用
	strcpy(str,"hello world");//向NULL指针空间拷贝"hello world"必出段错误
	printf(str);//格式不对,而且用malloc申请堆空间,必用free手动释放
  • 有int a=10;int* pa=&a;
    则(&a)++和pa++的区别
    (&a)++ : 非法的。&a是常量,常量无法作为左值。
    pa++: 合法的。pa是变量,pa++相当于是对pa中存储的地址值做++运算。int b=10; b++;
  • 指针运算
    1. 对指针进行算数运算,实际上是对指针进行偏移
    2. 对指针进行+1操作,偏移量是:指针指向的数据类型的大小
      int* pa . pa+1,偏移4个字节
      char* pb; pb+1,偏移1个字节
      short* pc; pc+1,偏移2个字节
      long* pd; pd+1, 偏移8个字节(64位OS) ,偏移4个字节(32位OS)
    3. *号和++运算符优先级为同级,但是是自右向左计算
      pa++ : 先计算pa++,后置++先返回pa的值,然后将pa自增。*号是和偏移前的pa结合
      *(pa++):同
      pa++
      *++pa : 先计算++pa,是个前置++(先pa自增,pa先偏移到0xa5,再返回pa的值)
  • 指针与字符串
    1. 指针与字符串,就是指针与一维数组。因为字符串是存储在char类型数组中。
    2. "1234"是常量字符串,常量字符串单独使用代表了常量字符串的首地址。
    3. %s占位符,对应的数据类型char*类型。原理:从给定的字符地址位置开始打印,直到遇到’\0’字符停止
    4. 未定义行为
char *p = "hello";//这一行代码定义了一个指向字符串常量 "hello" 的指针 p。在C语言中,字符串常量通常存储在只读内存区域中,因此通过指针 p 修改字符串常量的内容是未定义行为,会导致程序崩溃或者异常。
*p = 'a';//非法行为
p++;//可以正常运行
----------------------------------
如果希望修改字符串的内容,应将其定义为一个字符数组,而不是指向字符串常量的指针。字符数组存储在可写的内存区域中,允许修改其内容。
  • 动态分配内存
  1. 引入头文件
#include <stdlib.h>
  1. 调用 malloc 函数
void* ptr = malloc(size);
  1. 检查 malloc 的返回值
if (ptr == NULL) {
    // 处理内存分配失败的情况
}
  1. 使用分配的内存

  2. 释放分配的内存


free(ptr);

6.范例:分配一个结构体数组

#include <stdio.h>  // 包含标准输入输出库头文件,用于使用 printf 函数
#include <stdlib.h> // 包含标准库头文件,用于使用 malloc 和 free 函数

// 定义一个名为 Point 的结构体,包含两个整数成员 x 和 y
typedef struct {
    int x;
    int y;
} Point;

int main() {
    int n = 3;  // 定义一个整数 n 并初始化为 3,表示我们将分配 3 个 Point 结构体的内存

    // 使用 malloc 函数动态分配 n 个 Point 结构体大小的内存
    // malloc 返回一个 void* 类型的指针,这里需要将其转换为 Point* 类型
    Point *points = (Point*)malloc(n * sizeof(Point));
    
    // 检查 malloc 返回的指针是否为 NULL,以确保内存分配成功
    if (points == NULL) {
        printf("Memory allocation failed\n"); // 如果内存分配失败,打印错误信息
        return 1; // 返回非零值表示程序异常终止
    }

    // 使用 for 循环初始化分配的 Point 结构体数组
    for (int i = 0; i < n; i++) {
        points[i].x = i;       // 设置第 i 个 Point 的 x 成员为 i
        points[i].y = i * 2;   // 设置第 i 个 Point 的 y 成员为 i 的两倍
    }

    // 使用 for 循环打印每个 Point 结构体的成员值
    for (int i = 0; i < n; i++) {
        printf("Point %d: (%d, %d)\n", i, points[i].x, points[i].y); // 打印第 i 个 Point 的 x 和 y 值
    }

    // 使用 free 函数释放先前分配的内存,防止内存泄漏
    free(points);
    
    return 0; // 返回 0 表示程序成功执行
}

  • 内存分布
    在这里插入图片描述
  1. 代码段(Text Segment)
    存储程序的可执行代码(机器指令)。
    通常是只读的,以防止程序意外修改其指令。
  2. 数据段(Data Segment)
    分为已初始化数据段和未初始化数据段。
    已初始化数据段(Initialized Data Segment):存储已初始化的全局变量和静态变量。
    未初始化数据段(BSS Segment):存储未初始化的全局变量和静态变量,程序启动时自动初始化为零。
  3. 堆(Heap)
    用于动态内存分配,程序运行时可以使用 malloc、calloc、realloc 等函数从堆中分配内存,使用 free 释放内存。
    堆内存的分配和释放由程序员控制,不受作用域限制。
  4. 栈(Stack)
    用于存储函数调用的上下文(局部变量、函数参数、返回地址等)。
    栈内存自动管理,函数调用时分配内存,函数返回时释放内存。
    栈内存有固定的大小限制,过深的递归调用或过大的局部变量分配可能导致栈溢出。
  5. 常量区(Literal or Constant Segment)
    存储程序中的常量,包括字符串字面量和 const 变量。
    这些常量通常是只读的,防止程序修改常量的值。
  • 23
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值