指针详解之 多层嵌套的关系

1 例子之指向3个字符串的指针数组,易混淆!

1.1过程详解:

char *str[3]={ 
    "Hello,thisisasample!", 
    "Hi,goodmorning.", 
    "Helloworld" 
}; 
char s[80]; 
strcpy(s,str[0]); //也可写成strcpy(s,*str); 
strcpy(s,str[1]); //也可写成strcpy(s,*(str+1)); 
strcpy(s,str[2]); //也可写成strcpy(s,*(str+2));

上例中,str 是一个三单元的数组,该数组的每个单元都是一个指针,这些指针各指向一个字符串。把指针数组名str 当作一个指针的话,它指向数组的第0 号单元,它的类型是char **,它指向的类型是char *。
*str 也是一个指针,它的类型是char *,它所指向的类型是char,它指向的地址是字符串"Hello,thisisasample!"的第一个字符的地址,即'H'的地址。注意:字符串相当于是一个数组,在内存中以数组的形式储存,只不过字符串是一个数组常量,内容不可改变,且只能是右值.如果看成指针的话,他即是常量指针,也是指针常量.
str+1 也是一个指针,它指向数组的第1 号单元,它的类型是char**,它指向的类型是char*。
*(str+1)也是一个指针,它的类型是char*,它所指向的类型是char,它指向"Hi,goodmorning."的第一个字符'H'

1.2 分析:哪里易错

在你提供的代码片段中,char *str[3] 定义了一个包含三个元素的字符指针数组,每个元素都是一个指向字符(char)的指针。这些指针分别初始化为指向三个字符串字面量。

现在我们具体来看 str*str 在这种上下文中的含义:

1 str/*str 的指针类型、指向的类型、内部的值

  • 类型:str 是一个指针数组,其类型是 char *[3],也就是说它是一个含有三个 char * 类型元素的数组。
  • 值:str 本身代表这个数组的起始地址,即第一个指针 str[0] 的地址。因此,str 可以被视为一个指向 char * 类型的指针,即 char **
  • 使用场景:当你使用 str 时,你可以通过索引来访问数组中的各个指针,例如 str[0]str[1] 和 str[2],它们分别指向不同的字符串。
     

2 *str 或 str[0]

  • 类型:*str 是对 str 进行一次解引用的结果。由于 str 是一个 char *[3] 类型的数组,*str 的类型就是 char *,也就是指向字符的指针。
  • 值:*str 等同于 str[0],它指向了第一个字符串 "Hello,thisisasample!" 的第一个字符 'H'
  • 使用场景:当你使用 *str 时,实际上是在访问 str 数组的第一个元素,也就是指向第一个字符串的指针。如果你想访问该字符串中的字符,你可以进一步解引用,例如 **str 或 (*str)[0] 将会得到字符 'H'

3 具体例子

假设你有以下代码:

c  深色版本

#include <stdio.h>

int main() {
    char *str[3] = { 
        "Hello,thisisasample!", 
        "Hi,goodmorning.", 
        "Helloworld" 
    };

    // 打印 str 的值,即指针数组的地址
    printf("Address of str: %p\n", (void *)str);

    // 打印 *str 的值,即第一个指针所指向的地址
    printf("Address pointed by *str: %p\n", (void *)*str);

    // 打印 *str 解引用后的值,即第一个字符串的第一个字符
    printf("First character of the first string: %c\n", **str);

    // 打印第二个指针所指向的字符串
    printf("Second string: %s\n", str[1]);

    return 0;
}

这段代码将会输出类似如下的结果(具体的内存地址会有所不同):

 

深色版本

Address of str: 0x7ffee4b8a960
Address pointed by *str: 0x105f41010
First character of the first string: H
Second string: Hi,goodmorning.

在这个例子中,str 表示整个指针数组的地址,而 *str 表示数组中第一个元素(即第一个字符串的指针)的值。**str 则是进一步解引用,得到了第一个字符串的第一个字符。

3.数组和结构体


3.1结构体用法

struct  book
    {
        /* data */
        char name[MaxL];
        char press[MaxL];
        float price;
    };
    
    char *str[3] = {"123",
                    "456",
                    "789"};
    // printf("%s \n",typeof(*str));
    // printf("%s \n",typeof(str));
    struct book zgs = {
        "哈佛史",
        "asml出版社",
        100
    };
    printf(" 名字:%s \n 出版社:%s \n 价格:%f 'n",
    zgs.name,zgs.press,zgs.price);
    

 


3.1  结构体易错点

 

不能直接结构体的指针赋值:必须要用strcpy(s1.name,"张三");

3.2 代码:
 

#include<stdio.h>
#include<string.h>

int main()
{
	struct student
	{
		char name[20];
		int age;
		char sex;
	};
    struct student s1 ;
    strcpy(s1.name,"zhangsan");
    s1.age = 19;
    s1.sex = 'm';
    printf("%s %d %c",s1.name,s1.age,s1.sex);
    // // {
    //     "zhangsan",
    //     18,
    //     'm'
    // }


    return 0 ;
}


4 指针和函数


4.1求一个字符串的ascii码之和

#include <stdio.h>

int fun(char *s)
{
    int num = 0;
    for (int i = 0; *s!= '\0';)
    {
        num += *s;
        s++;
    }
    return num;
}
int main(void)
{

    char str[] = "asdflkadlkgaslkdlkdfsjlkdsfjdlkfj87UHJNBN*&^^)(*&^) &**&^TYH";
    // int fun(char *str );
    // int num = 0;

    int out = fun(str);

    printf("%d\n", out);
    return 0;
}

截图:

函数申明、引用、循环条件
 

5.指针安全问题

5.1指针类型转换与越界写入

char s = 'a';
int *ptr;
ptr = (int *)&s;
*ptr = 1298;
1. 代码解析
  • 变量声明char s = 'a'; 声明了一个字符变量 s,并将其初始化为字符 'a'。在内存中,s 占用一个字节。
  • 指针声明int *ptr; 声明了一个指向 int 类型的指针 ptr
  • 类型转换ptr = (int *)&s; 将 s 的地址强制转换为 int * 类型,并赋值给 ptr。这意味着 ptr 现在指向 s 的首地址,但它的类型是 int *,而不是 char *
  • 写入操作*ptr = 1298; 试图通过 ptr 写入一个整数值 1298 到 s 所在的内存位置。
2. 问题分析
a. 内存布局

在32位系统中,int 类型占用4个字节,而 char 类型只占用1个字节。因此,*ptr = 1298; 这条语句不仅仅是改变了 s 所占的一个字节,还会同时改变 s 后面相邻的三个字节。具体来说:

  • s 占用的内存位置假设为 0x1000
  • *ptr = 1298; 实际上会将 1298(即 0x00000512)写入从 0x1000 开始的四个字节中:
    • 0x10000x12(最低有效字节)
    • 0x10010x05
    • 0x10020x00
    • 0x10030x00(最高有效字节)
b. 越界写入的影响

由于 s 只占用一个字节,而 *ptr = 1298; 写入了四个字节,因此会覆盖 s 后面的三个字节。这些字节可能是其他变量、数据结构的一部分,甚至是程序的代码段或栈中的重要数据。具体影响取决于这些字节的内容:

  • 覆盖其他变量:如果 s 后面的三个字节属于其他变量,那么这些变量的值会被意外修改,导致程序行为异常。
  • 破坏栈帧:如果 s 是局部变量,位于栈中,那么 *ptr = 1298; 可能会破坏栈帧,导致函数返回地址或其他栈上的数据被篡改,进而引发程序崩溃或未定义行为。
  • 覆盖代码段:在某些情况下,*ptr = 1298; 可能会覆盖程序的代码段,导致程序执行非法指令,直接崩溃或产生不可预测的行为。
c. 未定义行为

C语言标准规定,当程序访问未分配的内存或超出变量范围时,会导致未定义行为(undefined behavior)。未定义行为意味着编译器和运行时环境可以以任何方式处理这种情况,包括但不限于程序崩溃、数据损坏、甚至看似正常运行但实际上隐藏了严重的安全隐患

3. 避免此类错误的建议

为了避免这种类型的错误,开发者应该遵循以下最佳实践:

a. 避免不必要的类型转换

类型转换(尤其是强制类型转换)应该谨慎使用。在本例中,将 char * 强制转换为 int * 是不安全的,因为它忽略了类型系统的约束,导致越界写入。如果确实需要进行类型转换,应该确保目标类型和源类型的大小一致,并且不会导致内存越界。

b. 使用适当的数据类型

选择合适的数据类型来存储和操作数据。如果只需要操作单个字符,应该使用 char 类型的指针,而不是 int *。这样可以避免不必要的内存访问和越界风险。

c. 检查指针的有效性

在使用指针之前,始终检查它是否指向有效的内存区域。可以通过以下方式减少错误:

  • 初始化指针:在声明指针时,确保它被正确初始化为 NULL 或指向有效的内存地址。
  • 释放内存后置空:在释放动态分配的内存后,立即将指针设为 NULL,防止悬垂指针

    18

  • 边界检查:在对数组或缓冲区进行操作时,确保指针不会超出其合法范围。
d. 使用现代工具和技术

现代编程语言和工具提供了许多机制来帮助开发者避免指针误用:

  • 静态分析工具:使用静态代码分析工具(如 Clang Static Analyzer、Cppcheck 等)可以在编译时检测潜在的指针错误。
  • 编译器警告:启用编译器的所有警告选项,并确保修复所有警告。编译器通常会提示可能的指针误用或类型不匹配问题。
e. 理解内存模型

深入了解系统的内存模型和字节序(endianness)对于编写正确的指针操作代码至关重要。不同的系统可能有不同的字节序(大端或小端),这会影响多字节数据的存储顺序。例如,在小端系统中,int 类型的值 1298 会被存储为 0x12 0x05 0x00 0x00,而在大端系统中则是 0x00 0x00 0x05 0x12。如果不了解这一点,可能会导致跨平台移植时出现错误


 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/943635.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Keras2.0 ImageDataGenerator 适配

最近在学习keras,总遇到使用 ImageDataGenerator当作训练参数&#xff0c;使用 fit_generator(), 而 Keras 2.0 已经放弃了&#xff0c;导致执行不下去了 经过N多天的摸索&#xff0c;终于是成功了 # 训练集数据生成 datagen ImageDataGenerator(rotation_range40,rescale…

循环神经网络(RNN)入门指南:从原理到实践

目录 1. 循环神经网络的基本概念 2. 简单循环网络及其应用 3. 参数学习与优化 4. 基于门控的循环神经网络 4.1 长短期记忆网络&#xff08;LSTM&#xff09; 4.1.1 LSTM的核心组件&#xff1a; 4.2 门控循环单元&#xff08;GRU&#xff09; 5 实际应用中的优化技巧 5…

低代码开源项目Joget的研究——Joget8社区版安装部署

大纲 环境准备安装必要软件配置Java配置JAVA_HOME配置Java软链安装三方库 获取源码配置MySql数据库创建用户创建数据库导入初始数据 配置数据库连接配置sessionFactory&#xff08;非必须&#xff0c;如果后续保存再配置&#xff09;编译下载tomcat启动下载aspectjweaver移动jw…

赋能开发者 | 麒麟信安受邀参加2024开放原子开发者大会,以技术为引领,以人才创发展

12月20至21日&#xff0c;以“一切为了开发者”为主题的“2024开放原子开发者大会暨首届开源技术学术大会”在湖北武汉举办。本届大会由开放原子开源基金会、中国通信学会联合主办&#xff0c;旨在贯彻落实国家软件发展战略&#xff0c;加速培育壮大我国开源生态。工业和信息化…

HTML5实现好看的喜庆圣诞节网站源码

HTML5实现好看的喜庆圣诞节网站源码 前言一、设计来源1.1 主界面1.2 圣诞介绍界面1.3 圣诞象征界面1.4 圣诞活动界面1.5 圣诞热度界面1.6 圣诞纪念界面1.7 联系我们界面 二、效果和源码2.1 动态效果2.2 源代码 源码下载结束语 HTML5实现好看的喜庆圣诞节网站源码&#xff0c;圣…

1.微服务灰度发布落地实践(方案设计)

前言 微服务架构中的灰度发布&#xff08;也称为金丝雀发布或渐进式发布&#xff09;是一种在不影响现有用户的情况下&#xff0c;逐步将新版本的服务部署到生产环境的策略。通过灰度发布&#xff0c;你可以先将新版本的服务暴露给一小部分用户或特定的流量&#xff0c;观察其…

Vue中动态样式绑定+CSS变量实现切换明暗主题功能——从入门到进阶

1.直接借助Vue的动态绑定样式绑定 Vue动态样式绑定 在Vue中&#xff0c;动态样式绑定是一种强大的功能&#xff0c;它允许开发者根据数据的变化动态地更新元素的样式。以下是对Vue动态样式绑定的详细知识梳理与详解&#xff1a; 一、基础知识 Vue的动态样式绑定主要通过v-b…

华为管理变革之道:奋斗文化与活力

目录 企业文化是什么&#xff1f; 为什么活下去是华为的文化&#xff1f; 活下来&#xff0c;是华为公司的最低纲领&#xff0c;也是华为公司的最高纲领&#xff01; 资源终会枯竭&#xff0c;唯有文化才能生生不息 企业文化之一&#xff1a;以客户为中心 企业文化之二&a…

强化数据治理能力,夯实数字政府建设基石!

当下&#xff0c;数字政府建设已成为推动国家治理体系和治理能力现代化的关键路径。数据作为数字化时代的关键生产要素&#xff0c;直接影响着数字政府建设的能效&#xff0c;关系着政府决策的科学性、公共服务的精准性以及社会治理的有效性。因此&#xff0c;通过数据治理来全…

NFC 碰一碰发视频源码搭建技术详解,支持OEM

一、引言 NFC&#xff08;Near Field Communication&#xff09;近场通信技术以其便捷性和安全性在现代移动应用中得到了广泛应用。结合视频播放功能&#xff0c;实现 NFC 碰一碰发视频的应用场景&#xff0c;能够为用户带来全新的交互体验&#xff0c;例如在商场的产品推广、景…

【论文阅读】AllMatch: Exploiting All Unlabeled Data for Semi-Supervised Learning

一、引言 在当今的机器学习领域&#xff0c;半监督学习&#xff08;SSL&#xff09;作为一种重要的学习范式&#xff0c;受到了广泛的关注。它旨在利用有限的标记数据和大量的未标记数据来提升模型的性能&#xff0c;从而在数据标记成本较高而未标记数据丰富的情况下发挥重要作…

光谱相机与普通相机的区别

一、成像目的 普通相机&#xff1a;主要目的是记录物体的外观形态&#xff0c;生成人眼可见的、直观的二维图像&#xff0c;重点在于还原物体的形状、颜色和纹理等视觉特征&#xff0c;以供人们进行观赏、记录场景或人物等用途。例如&#xff0c;拍摄旅游风景照片、人物肖像等…

基于单片机的蓄电池内阻检测系统设计(论文+源码)

1 系统的功能及方案设计 在本次设计中&#xff0c;考虑到整体设计难度。在此选择了上述的方法一来作为本次蓄电池内阻检测的方案。其系统整个框图如下图1所示。其主要的核心控制模块由LCD显示模块&#xff0c;负载电路模块&#xff0c;AD模数转换模块&#xff0c;继电器控制模…

Git核心概念

版本控制 什么是版本控制 版本控制是一种记录一个或若干文件内容变化&#xff0c;以便将来查阅特定版本修订情况的系统。 除了项目源代码&#xff0c;你可以对任何类型的文件进行版本控制。 为什么要版本控制 有了它你就可以将某个文件回溯到之前的状态&#xff0c;甚至将整…

Kotlin 协程基础知识总结二 —— 启动与取消

协程启动与取消的主要内容&#xff1a; 启动协程&#xff1a;启动构建器、启动模式、作用域构建器、Job 生命周期取消协程&#xff1a;协程的取消、CPU 密集型任务取消、协程取消的副作用、超时任务 1、协程构建器 &#xff08;P20&#xff09;launch 与 aysnc 两种协程构建…

kong网关使用pre-function插件,改写接口的返回数据

一、背景 kong作为api网关&#xff0c;除了反向代理后端服务外&#xff0c;还可对接口进行预处理。 比如本文提及的一个小功能&#xff0c;根据http header某个字段的值&#xff0c;等于多少的时候&#xff0c;返回一个固定的报文。 使用到的kong插件是pre-function。 除了上…

群落生态学研究进展▌Hmsc包对于群落生态学假说的解读、Hmsc包开展单物种和多物种分析的技术细节及Hmsc包的实际应用

HMSC&#xff08;Hierarchical Species Distribution Models&#xff09;是一种用于预测物种分布的统计模型。它在群落生态学中的应用广泛&#xff0c;可以帮助科学家研究物种在不同环境条件下的分布规律&#xff0c;以及预测物种在未来环境变化下的潜在分布范围。 举例来说&a…

MacroSan 2500_24A配置

双控制器电源同时按下,切记/切记/切记 默认信息 默认地址:192.168.0.210 输入ODSP授权后设置密码## 配置端口 物理资源–>设备–>网口–>eth-1:0:0或eth-2:0:0 创建存储池 存储资源–>存储池 介质类型:混合(支持机械及SSD)全闪(仅支持SSD) RAID类型:CRAID-P(基于磁…

SQL-leetcode-180. 连续出现的数字

180. 连续出现的数字 表&#xff1a;Logs -------------------- | Column Name | Type | -------------------- | id | int | | num | varchar | -------------------- 在 SQL 中&#xff0c;id 是该表的主键。 id 是一个自增列。 找出所有至少连续出现三次的数字。 返回的…

ISDP010_基于DDD架构实现收银用例主成功场景

信息系统开发实践 &#xff5c; 系列文章传送门 ISDP001_课程概述 ISDP002_Maven上_创建Maven项目 ISDP003_Maven下_Maven项目依赖配置 ISDP004_创建SpringBoot3项目 ISDP005_Spring组件与自动装配 ISDP006_逻辑架构设计 ISDP007_Springboot日志配置与单元测试 ISDP008_SpringB…