指针详解之 多层嵌套的关系
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开始的四个字节中:0x1000:0x12(最低有效字节)0x1001:0x050x1002:0x000x1003:0x00(最高有效字节)
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。如果不了解这一点,可能会导致跨平台移植时出现错误
相关文章:
指针详解之 多层嵌套的关系
1 例子之指向3个字符串的指针数组,易混淆! 1.1过程详解: char *str[3]{ "Hello,thisisasample!", "Hi,goodmorning.", "Helloworld" }; char s[80]; strcpy(s,str[0]); //也可写成strcpy(s,*st…...
Animated Drawings:让纸上的角色动起来
前言 今天介绍的这个工具非常的有意思:它可以让我们在纸上绘画的角色动起来。先一起来看看效果: 准备 首先,我们先准备一张绘画。可以在纸上进行绘制,也可以在电子设备上进行绘制。绘制内容不限,在这里为了方便演示&am…...
技术与教育的结合:高校听课评价系统的设计与实施
3.1系统可行性分析 需要使用大部分精力开发的高校听课评价系统为了充分降低开发风险,特意在开发之前进行可行性分析这个验证系统开发是否可行的步骤。本文就会从技术角度,经济角度,还有用户使用的程序的运行角度进行综合阐述。 3.1.1 技术可行…...
web移动端项目常用解决方案
移动端总会遇到一系列特定于移动设备的问题,分享下常见的移动端常见问题的处理方案。 1px边框问题 在高清屏幕下,1px的边框显示得比较粗。 .border-1px {position: relative; } .border-1px::after {position: absolute;content: ;width: 200%;height:…...
LabVIEW软件项目设计方案如何制定
制定LabVIEW软件项目设计方案需要综合考虑需求分析、架构设计、功能模块划分和时间预算等多个方面,确保项目开发过程高效、可控且最终满足目标要求。以下是一个详细的制定流程: 1. 需求分析 目标定义:明确项目的目标,例如数据采…...
数据结构(Java)——链表
1.概念及结构 链表是一种 物理存储结构上非连续 存储结构,数据元素的 逻辑顺序 是通过链表中的 引用链接 次序实现的 。 2.分类 链表的结构非常多样,以下情况组合起来就有 8 种链表结构: (1)单向或者双向 (…...
变量与数据类型 - 整型、浮点型、字符型等
引言 在编程中,变量和数据类型是基础中的基础。理解它们如何工作以及如何正确使用它们对于编写高效且无误的代码至关重要。本文将详细介绍 C 中的几种基本数据类型:整型、浮点型、字符型等,并通过实例帮助读者更好地理解和掌握这些概念。 一…...
MacOS安装Xcode(非App Store)
文章目录 访问官网资源页面 访问官网资源页面 直接访问官网的历史版本下载资源页面地址:https://developer.apple.com/download/more/完成APP ID的登陆,直接找到需要的软件下载即可 解压后,安装将xcode.app移动到应用程序文件夹。...
运行Zr.Admin项目(后端)
1.下载Zr.Admin代码压缩包 https://codeload.github.com/izhaorui/Zr.Admin.NET/zip/refs/heads/main 2.打开项目 我这里装的是VS2022社区版 进入根目录,双击ZRAdmin.sln打开项目 3.安装.net7运行时 我当时下载的代码版本是.net7的 点击安装 点击安装࿰…...
Ubuntu24.04最新版本安装详细教程
Ubuntu 24.04 LTS发布说明 推荐的系统配置要求: 双核2 GHz处理器或更高 4 GB系统内存 25 GB磁盘存储空间 可访问的互联网 光驱或USB安装介质 Ubuntu 24.04官方下载网址:https://cn.ubuntu.com/download/desktop 04. Ubuntu 22.04(创建虚拟机方式一) 4…...
js版本之ES6特性简述【Proxy、Reflect、Iterator、Generator】(五)
目录 Proxy Reflect 静态方法 部分实例 Iterator 实际开发迭代器的使用实例 迭代器(Iterator)应用 Generator Proxy Proxy 是 ES6 中新增的对象 Proxy 是JavaScript中的内置对象,它提供了一种机制,可以拦截并自定义各种…...
CSS实现一个自定义的滚动条
要使用CSS创建一个自定义的滚动条,你可以使用伪元素和CSS的伪类来控制滚动条的外观和行为。以下是一个简单的例子,展示如何为任何HTML元素添加一个自定义的滚动条样式: <!DOCTYPE html> <html lang"en"> <head> …...
CKA认证 | Day8 K8s安全
第八章 Kubernetes安全 1、Kubernetes RBAC授权 Kubernetes 基于角色的访问控制(Role-Based Access Control, RBAC) 是一种强大的权限管理机制,用于控制用户、用户组、服务账户对 Kubernetes 集群资源的访问。通过 RBAC,可以细…...
深度分析java 使用 proguard 如何解析混淆后的堆栈
经过proguard混淆过后,发生异常时堆栈也进行了混淆,那么如果获取的原始的堆栈呢?我们下面来看下 使用proguard 根据mapping文件直接解析 import proguard.obfuscate.MappingReader; import proguard.retrace.FrameInfo; import proguard.re…...
bash 中 ${-#*i} 是什么意思?
-------------------------------------------------- author: hjjdebug date: 2024年 12月 25日 星期三 17:43:45 CST description: bash 中 ${-#*i} 是什么意思? -------------------------------------------------- 在centos 的 /etc/profile 中有这样的语句 for i in /…...
什么是Top-p采样与Top-k采样?大模型推理时如何同时设置?解析Transformers库源代码
什么是Top-p采样与Top-k采样?大模型推理时如何同时设置? 在自然语言生成(NLG)和大规模语言模型推理中,Top-k采样 和 Top-p采样 是两种常用的解码策略,用于控制生成文本的多样性和质量。它们在生成过程中对…...
java队列--数据结构
文章目录 前言本文源代码网址:https://gitee.com/zfranklin/java/tree/master/dataStructure/src/com/njupt/queue队列的性质数组队列成员变量方法 链表栈成员变量方法 总结 前言 顺序表和链表两种存储方式实现数据结构–队列。 本文源代码网址:https:/…...
【WebSocket】tomcat内部处理websocket的过程
websocket请求格式 浏览器请求 GET /webfin/websocket/ HTTP/1.1。 Host: localhost。 Upgrade: websocket。 Connection: Upgrade。 Sec-WebSocket-Key: xqBt3ImNzJbYqRINxEFlkg。 Origin: http://服务器地址。 Sec-WebSocket-Version: 13。 服务器响应 HTTP/1.1 101 Swi…...
【踩坑/Linux】Vmware中的Ubuntu虚拟机无法访问互联网
Vmware中的Ubuntu虚拟机无法访问互联网 首先前提是我的系统是Ubuntu 16.04系统,vmware workstation选择的是NAT模式,虚拟机内连不上网络 ping www.baidu.com ping: unknown host www.baidu.com首先检查 DNS 解析服务:在虚拟机中打开命令提示…...
overleaf中的includegraphics设置图片缩放,居中显示
overleaf中的includegraphics设置图片缩放,居中显示 \includegraphics[width=0.5\textwidth]{example.jpg} \centering 在使用 \includegraphics 命令插入图片时,可以通过设置其参数来缩小图片的显示尺寸,以下是几种常见的方法: 设置宽度或高度 按比例缩小宽度:可以使用…...
rknn优化教程(二)
文章目录 1. 前述2. 三方库的封装2.1 xrepo中的库2.2 xrepo之外的库2.2.1 opencv2.2.2 rknnrt2.2.3 spdlog 3. rknn_engine库 1. 前述 OK,开始写第二篇的内容了。这篇博客主要能写一下: 如何给一些三方库按照xmake方式进行封装,供调用如何按…...
【CSS position 属性】static、relative、fixed、absolute 、sticky详细介绍,多层嵌套定位示例
文章目录 ★ position 的五种类型及基本用法 ★ 一、position 属性概述 二、position 的五种类型详解(初学者版) 1. static(默认值) 2. relative(相对定位) 3. absolute(绝对定位) 4. fixed(固定定位) 5. sticky(粘性定位) 三、定位元素的层级关系(z-i…...
SpringCloudGateway 自定义局部过滤器
场景: 将所有请求转化为同一路径请求(方便穿网配置)在请求头内标识原来路径,然后在将请求分发给不同服务 AllToOneGatewayFilterFactory import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; impor…...
Python 包管理器 uv 介绍
Python 包管理器 uv 全面介绍 uv 是由 Astral(热门工具 Ruff 的开发者)推出的下一代高性能 Python 包管理器和构建工具,用 Rust 编写。它旨在解决传统工具(如 pip、virtualenv、pip-tools)的性能瓶颈,同时…...
C++使用 new 来创建动态数组
问题: 不能使用变量定义数组大小 原因: 这是因为数组在内存中是连续存储的,编译器需要在编译阶段就确定数组的大小,以便正确地分配内存空间。如果允许使用变量来定义数组的大小,那么编译器就无法在编译时确定数组的大…...
Python Einops库:深度学习中的张量操作革命
Einops(爱因斯坦操作库)就像给张量操作戴上了一副"语义眼镜"——让你用人类能理解的方式告诉计算机如何操作多维数组。这个基于爱因斯坦求和约定的库,用类似自然语言的表达式替代了晦涩的API调用,彻底改变了深度学习工程…...
Unity UGUI Button事件流程
场景结构 测试代码 public class TestBtn : MonoBehaviour {void Start(){var btn GetComponent<Button>();btn.onClick.AddListener(OnClick);}private void OnClick(){Debug.Log("666");}}当添加事件时 // 实例化一个ButtonClickedEvent的事件 [Formerl…...
uniapp 小程序 学习(一)
利用Hbuilder 创建项目 运行到内置浏览器看效果 下载微信小程序 安装到Hbuilder 下载地址 :开发者工具默认安装 设置服务端口号 在Hbuilder中设置微信小程序 配置 找到运行设置,将微信开发者工具放入到Hbuilder中, 打开后出现 如下 bug 解…...
Ubuntu系统多网卡多相机IP设置方法
目录 1、硬件情况 2、如何设置网卡和相机IP 2.1 万兆网卡连接交换机,交换机再连相机 2.1.1 网卡设置 2.1.2 相机设置 2.3 万兆网卡直连相机 1、硬件情况 2个网卡n个相机 电脑系统信息,系统版本:Ubuntu22.04.5 LTS;内核版本…...
GAN模式奔溃的探讨论文综述(一)
简介 简介:今天带来一篇关于GAN的,对于模式奔溃的一个探讨的一个问题,帮助大家更好的解决训练中遇到的一个难题。 论文题目:An in-depth review and analysis of mode collapse in GAN 期刊:Machine Learning 链接:...

