首页 > 其他分享 >嵌入式基础 C语言篇 指针初阶

嵌入式基础 C语言篇 指针初阶

时间:2025-01-18 20:28:20浏览次数:3  
标签:初阶 int NULL 嵌入式 char 地址 内存 C语言 指针

一、指针的入门

(1)、预备知识


0、图解:

1、内存地址

  • 字节:字节是内存的容量单位,英文称为 byte,一个字节有8位,即 1byte(0000 0000 --- 1111 1111) = 8bits(0 --- 1)
  • 地址:系统为了便于区分每一个字节而对它们逐一进行的编号,称为内存地址,简称地址。

在32位系统:

说明:

地址+1就是加1个字节

为什么选32位系统来讲内存??

32位:4G内存:

64位:理论:1800亿亿GB,操作系统支持:16TB,电脑实际的内存条:4G-32G(所以本质上还是基于4G)

2、基地址

  • 单字节数据:对于单字节数据而言,其地址就是其字节编号。
  • 多字节数据:对于多字节数据而言,其地址是其所有字节中编号最小的那个,称为基地址。

3、取址符

  • 每个变量都是一块内存,都可以通过取址符 & 获取其地址
  • 注意:
    • 虽然不同的变量的尺寸是不同的,但是他们的地址的尺寸确实一样的。
    • 不同的地址虽然形式上看起来是一样的,但由于他们代表的内存尺寸和类型都不同,因此它们在逻辑上是严格区分的。
  • 示例代码:
    // (1)、如何获取一个内存的地址??
    char   ch1 = 200;
    int   num1 = 100;
    float  f1  = 3.14;
    double f2  = 6.18;
    
    // 1、不同的变量的尺寸是不同的
    printf("ch1的地址  == %p\n", &ch1);
    printf("num1的地址 == %p\n", &num1);
    printf("f1的地址   == %p\n", &f1);
    printf("f2的地址   == %p\n", &f2);
    /*
        // 3- 不同的地址虽然形式上看起来是一样的,但由于他们代表的内存尺寸和类型都不同,因此它们在逻辑上是严格区分的。
        解析:
            ch1的地址  == 0x7ffd1cf9c917        // char型内存占1字节
            num1的地址 == 0x7ffd1cf9c918        // int型内存占4字节
            f1的地址   == 0x7ffd1cf9c91c        // float型内存占4字节
            f2的地址   == 0x7ffd1cf9c920        // float型内存占8字节
    */
    
    // 2、但是他们的地址的尺寸确实一样的
    printf("ch1的地址的尺寸  == %lu\n", sizeof(&ch1));
    printf("num1的地址的尺寸 == %lu\n", sizeof(&num1));
    printf("f1的地址的尺寸   == %lu\n", sizeof(&f1));
    printf("f2的地址的尺寸   == %lu\n", sizeof(&f2));

    (2)、指针的概念

  • 0、图解:

    1、指针的说明

    由于翻译的问题,以及口语表达的习惯,在日常表述中,指针在不同的场合会代表以下几个含义:

  • 指 地址
    • 比如变量a的地址 &a,这是一个地址当然也是一个指针,我们可以说指针 &a 指向变量 a。
  • 指 指针变量
    • 比如 int *p; 此处变量p是指针变量,又常被简称指针。
  • 示例代码:
    // (1)、指针的说明
    // 1、指地址
    int num2 = 200;
    printf("num2的地址 == %p\n", &num2) ;   // 可以认为&num2为指针,这个指针&num指向了变量num2(或者num2的地址被&num2给存放了)
    
    // 2、指指针变量
    int num3 = 300;
    int *p1  = &num3;                      // 指针变量p里面存放了num3的内存的地址(指针变量p指向了num的内存)
    

    2、指针的初始化(定义)

    // 1、未初始化的类型                    // 注意:定义指针的时候,必须要有具体的合法指向,否则指针将会在内存中乱指,导致数据处理出错
    char   *p1;                            // 字符型指针类型
    int    *p2;                            // 整型指针类型
    float  *p3;                            // 浮点型指针类型
    double *p4;                            // 双精度浮点型指针类型
    
    // 2、推荐的初始化
    char ch  = 0;                         
    char *p5 = &ch;                        // 指向确定的我们申请的合法内存(可读可写)
    char *p6 = NULL;                       // 指向内存中一个临时的安全的保留区域(不可访问区域)
    

    3、指针的赋值

    int num4 = 100;
    int *p7  = NULL;
    p7   = &num4;                           // 一般性的赋值操作,和初始化(定义)是不一样的,不需要加*号,p8本身就是指针变量

    4、指针的索引(引用)

    int num5 = 5;
    num5     = 55;                         // 直接通过内存的名字,对其内存进行赋值操作
    
    int *p8  = &num5;                       // 让指针p8指向这块内存
    *p8      = 8;                         // 间接通过指针p8来控制num5的内存,然后给其赋值
    printf("num5 == %d\n", num5);
    
    /*
        int *p和*p的不同:
            int *p: 这个是初始化,只是表明这个p变量是个指针
            *p:    这个不是初始化,是后面的语句用的,这个时候表明其是指针p指向的那块内存
            p:      这个不是初始化,是后面的语句用的,这个时候表明其是指针变量p
    */

    二、特殊指针

    (1)、野指针

  • 概念:指向一块未知区域的指针,被称为野指针。野指针是危险的。
  • 危害:
    1. 引用野指针,相当于访问了非法的内存,常常会导致段错误(segmentation fault)
    2. 引用野指针,可能会破坏系统的关键数据,导致系统崩溃等严重后果
  • 产生原因:
    1. 指针定义之后,未初始化
    2. 指针所指向的内存,被系统回收
    3. 指针越界
  • 如何防止:
    1. 指针定义时,及时初始化
    2. 绝不引用已被系统回收的内存
    3. 确认所申请的内存边界,谨防越界
  • 示例代码:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    // 计算数组元素的个数
    #define CAL_ARR_NUM(A) (sizeof(A)/sizeof(A[0]))
                            // 整个数组的大小 / 数组的首元素的大小 == 数组元素个数
    
    // 自己定义的NULL
    #define SELF_NULL ((void *)0) 
    
    // 官方定义的NULL
    // #define NULL ((void *)0)   // 空指针
    
    // 主函数
    int main(int argc, char const *argv[])
    {
        // (1)、野指针
        // 1、指针定义之后,未初始化
        char *p1;
        /*
            解决方法:
                方法一:char *p1 = NULL;  // char *p1 =  SELF_NULL
                方法二:
                    char ch  = 'a';
                    char *p1 = &ch;
        */
    
        // 2、指针所指向的内存,被系统回收(笔试题)
        char *str = malloc(100);    // 申请堆内存空间:申请一块100个字节的内存空间(堆空间),str指针指向了这块内存
        strcpy(str, "hello");       // 通过str指针将"hello"字符串数据,复制到str指向的内存中(堆空间)
    
        // free(str);               // 释放堆内存空间:通过str指针释放其指向的内存空间
    
        if (str != NULL)            // str指针释放堆内存空间后,其指向依然是那个空间的位置,不会是NULL(具体看系统,看编译器)
        {
            strcpy(str, "world");   // 之前已经释放了str指针指向的堆内存空间了,对其已经没有访问权限了,所以会报错误或警告
            printf("str == %s\n", str);
        }
    
        // 解决方法:使用完该内存,再释放,绝不再次使用
    
        // 3、指针越界
        char buf[] = "shijie";
        char *p2   = buf;
    
        // 数组的地址
        for (int i = 0; i < 7; i++)
        {
            printf("buf[%d]的地址 == %p\n", i, &buf[i]);
        }
        
        // p2逐渐加1的地址
        for (int i = 0; i < 7; i++)
        {
            printf("p2+%d的地址 == %p\n", i, p2+i); // 将指针变量p2里面的地址打印出来
        }
        
        *(p2+10) = 'a';     // 此处不能赋值,因为越界了,不是我门申请的合法内存空间
        /*
            解决方法:
                让指针赋值操作在其限定的合法内存的区域上进行
                比如:使用这个函数CAL_ARR_NUM(A)   // 计算其合法内存区域的长度
        */
    
        return 0;
    }

    (2)、空指针

    很多情况下,我们不可避免地会遇到野指针,比如刚定义的指针内无法立即为其分配一块恰当的存,又或者指针所指向的内存被释放了等等。一般的做法就是将这些危险的野指针指向一块确定的内存,比如零地址内存。

  • 概念:空指针即保存了零地址的指针,亦即指向零地址的指针。
    // 自写的NULL空指针
    #define SELF_NULL ((void *)0)   
    
    // 官方定义的NULL空指针
    #define NULL ((void *)0)

    三、指针运算

  • (1)、计算指针的大小

  • 指针的大小(尺寸)   --- 只和系统位数相关,和数据类型无关 (char * int* 等等,大小都和系统位数有关,不和数据类型相关)
  • 那指针的数据类型有什么用呢?

  • 可以让指针确定其作用范围,或者其移动范围

标签:初阶,int,NULL,嵌入式,char,地址,内存,C语言,指针
From: https://blog.csdn.net/weixin_63053154/article/details/145231623

相关文章

  • 嵌入式基础 C语言篇 错题
    (1) 若已定义x和y为double类型,则表达式x=1,y=x+3/2的值是________。A.1     B.2    C.2.0    D.2.53/2=1;y=2.0(2)简述i++和++i    i++是先使用i的值,再i+1;++i是先i+1,再使用i的值底层原理:1、i++和++i都是带有返回值的函数2......
  • 第1讲:C语言常见概念
    目录1.C语言是什么?2.C语言的历史和辉煌3.编译器的选择VS20224.VS项目和源文件、头文件介绍5.第一个C语言程序6.main函数7.printf和库函数8.关键字介绍9.字符和ASCII编码10.字符串和\011.转义字符12.语句和语句分类13.注释是什么?为什么写注释?正文开始1.C语言......
  • 第2讲:C语言数据类型和变量
    目录1.数据类型介绍2.signed和unsigned3.数据类型的取值范围4.变量5.算术操作符:+、-、*、/、%6.赋值操作符:=和复合赋值7.单目操作符:++、--、+、-8.强制类型转换9.scanf和printf介绍正文开始1.数据类型介绍C语言提供了丰富的数据类型来描述生活中的各种数......
  • C语言中的流程控制(for循环 while循环 do-while循环)
    什么是循环?循环---重复在执行循环语句for while do-while 1. for循环for(表达式1;表达式2;表达式3) {  语句} ①求解表达式1  ②判断表达式2  真 ③执行语句④求解表达式3  重复②-④直到表达式2为假    ①只会执行一次  ......
  • 嵌入式知识点总结(一)-C/C++关键字
     针对于嵌入式软件杂乱的知识点总结起来,提供给读者学习复习对下述内容的强化。目录1.C语言宏中"#“和"##"的用法1.1.(#)字符串化操作符1.2.(##)符号连接操作符2.关键字volatile有什么含意?并举出三个不同的例子?2.1.并行设备的硬件寄存器2.2.中断服务程序中修改的变......
  • GCC支持Objective C的故事?Objective-C?GCC只能编译C语言吗?Objective-C 1.0和2.0有什
    GCC支持ObjectiveC的故事Objective-C主要由 Stepstone 公司的BradCox和 TomLove在1980年左右发明。乔布斯离开苹果公司后成立了NeXTSTEP公司, 买下了Objective-C语言的授权。GCC对Objective-C语言的支持是在1992年加入的,具体是在GCC1.3版本中首次引入的。G......
  • 嵌入式杂谈——什么是DMA?有什么用?
    什么是DMA?——直接内存访问技术详解在嵌入式系统和计算机体系结构中,DMA(DirectMemoryAccess,直接内存访问) 是一种重要的数据传输技术。它允许外设(如UART、SPI、ADC等)直接与内存进行数据交换,而无需CPU的干预。DMA技术可以显著提高系统的效率和性能,尤其是在需要高速数据传输的......
  • 华为2024嵌入式研发面试题
    01你认为最好的排序算法是什么?在实际的编程中,最好的排序算法要根据实际需求和数据规模来选择,因为每种排序算法都有其优势和劣势。以下是一些常见排序算法及其优缺点:冒泡排序冒泡排序是一种简单直观的排序算法,它的时间复杂度是O(n^2)。虽然它的时间复杂度比较高,但它的实现方......
  • C语言中char *str[] 和char *str有什么区别
    charstr[]和charstr的区别:C语言中charstr和charstr[]的区别-CSDN博客char*str[]和char*str在C语言中有不同的含义和用途,以下是它们的区别:1.char*str类型:这是一个指向字符的指针。用途:通常用于指向一个字符串。字符串在C语言中是一个以空字符'\0'结尾的字......
  • C语言的数据类型和变量
    在C语言中,数据类型主要分为两大类,一类是内置类型,另一类是自定义类型,今天主要来说一下内置类型一、数据类型1、字符型字符型:char(是character的缩写)signedchar表示有符号的字符型,unsigned插入表示无符号的字符型,2、整型3、浮点型浮点型主要有三种,分别是float(单精度浮点......