当前位置: 首页 > news >正文

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

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。如果不了解这一点,可能会导致跨平台移植时出现错误


 

相关文章:

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

1 例子之指向3个字符串的指针数组&#xff0c;易混淆&#xff01; 1.1过程详解&#xff1a; char *str[3]{ "Hello,thisisasample!", "Hi,goodmorning.", "Helloworld" }; char s[80]&#xff1b; strcpy(s,str[0]); //也可写成strcpy(s,*st…...

Animated Drawings:让纸上的角色动起来

前言 今天介绍的这个工具非常的有意思&#xff1a;它可以让我们在纸上绘画的角色动起来。先一起来看看效果&#xff1a; 准备 首先&#xff0c;我们先准备一张绘画。可以在纸上进行绘制&#xff0c;也可以在电子设备上进行绘制。绘制内容不限&#xff0c;在这里为了方便演示&am…...

技术与教育的结合:高校听课评价系统的设计与实施

3.1系统可行性分析 需要使用大部分精力开发的高校听课评价系统为了充分降低开发风险&#xff0c;特意在开发之前进行可行性分析这个验证系统开发是否可行的步骤。本文就会从技术角度&#xff0c;经济角度&#xff0c;还有用户使用的程序的运行角度进行综合阐述。 3.1.1 技术可行…...

web移动端项目常用解决方案

移动端总会遇到一系列特定于移动设备的问题&#xff0c;分享下常见的移动端常见问题的处理方案。 1px边框问题 在高清屏幕下&#xff0c;1px的边框显示得比较粗。 .border-1px {position: relative; } .border-1px::after {position: absolute;content: ;width: 200%;height:…...

LabVIEW软件项目设计方案如何制定

制定LabVIEW软件项目设计方案需要综合考虑需求分析、架构设计、功能模块划分和时间预算等多个方面&#xff0c;确保项目开发过程高效、可控且最终满足目标要求。以下是一个详细的制定流程&#xff1a; ​ 1. 需求分析 目标定义&#xff1a;明确项目的目标&#xff0c;例如数据采…...

数据结构(Java)——链表

1.概念及结构 链表是一种 物理存储结构上非连续 存储结构&#xff0c;数据元素的 逻辑顺序 是通过链表中的 引用链接 次序实现的 。 2.分类 链表的结构非常多样&#xff0c;以下情况组合起来就有 8 种链表结构&#xff1a; &#xff08;1&#xff09;单向或者双向 &#xff08;…...

变量与数据类型 - 整型、浮点型、字符型等

引言 在编程中&#xff0c;变量和数据类型是基础中的基础。理解它们如何工作以及如何正确使用它们对于编写高效且无误的代码至关重要。本文将详细介绍 C 中的几种基本数据类型&#xff1a;整型、浮点型、字符型等&#xff0c;并通过实例帮助读者更好地理解和掌握这些概念。 一…...

MacOS安装Xcode(非App Store)

文章目录 访问官网资源页面 访问官网资源页面 直接访问官网的历史版本下载资源页面地址&#xff1a;https://developer.apple.com/download/more/完成APP ID的登陆&#xff0c;直接找到需要的软件下载即可 解压后&#xff0c;安装将xcode.app移动到应用程序文件夹。...

运行Zr.Admin项目(后端)

1.下载Zr.Admin代码压缩包 https://codeload.github.com/izhaorui/Zr.Admin.NET/zip/refs/heads/main 2.打开项目 我这里装的是VS2022社区版 进入根目录&#xff0c;双击ZRAdmin.sln打开项目 3.安装.net7运行时 我当时下载的代码版本是.net7的 点击安装 点击安装&#xff0…...

Ubuntu24.04最新版本安装详细教程

Ubuntu 24.04 LTS发布说明 推荐的系统配置要求&#xff1a; 双核2 GHz处理器或更高 4 GB系统内存 25 GB磁盘存储空间 可访问的互联网 光驱或USB安装介质 Ubuntu 24.04官方下载网址&#xff1a;https://cn.ubuntu.com/download/desktop 04. Ubuntu 22.04(创建虚拟机方式一) 4…...

js版本之ES6特性简述【Proxy、Reflect、Iterator、Generator】(五)

目录 Proxy Reflect 静态方法 部分实例 Iterator 实际开发迭代器的使用实例 迭代器&#xff08;Iterator&#xff09;应用 Generator Proxy Proxy 是 ES6 中新增的对象 Proxy 是JavaScript中的内置对象&#xff0c;它提供了一种机制&#xff0c;可以拦截并自定义各种…...

CSS实现一个自定义的滚动条

要使用CSS创建一个自定义的滚动条&#xff0c;你可以使用伪元素和CSS的伪类来控制滚动条的外观和行为。以下是一个简单的例子&#xff0c;展示如何为任何HTML元素添加一个自定义的滚动条样式&#xff1a; <!DOCTYPE html> <html lang"en"> <head> …...

CKA认证 | Day8 K8s安全

第八章 Kubernetes安全 1、Kubernetes RBAC授权 Kubernetes 基于角色的访问控制&#xff08;Role-Based Access Control, RBAC&#xff09; 是一种强大的权限管理机制&#xff0c;用于控制用户、用户组、服务账户对 Kubernetes 集群资源的访问。通过 RBAC&#xff0c;可以细…...

深度分析java 使用 proguard 如何解析混淆后的堆栈

经过proguard混淆过后&#xff0c;发生异常时堆栈也进行了混淆&#xff0c;那么如果获取的原始的堆栈呢&#xff1f;我们下面来看下 使用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采样&#xff1f;大模型推理时如何同时设置&#xff1f; 在自然语言生成&#xff08;NLG&#xff09;和大规模语言模型推理中&#xff0c;Top-k采样 和 Top-p采样 是两种常用的解码策略&#xff0c;用于控制生成文本的多样性和质量。它们在生成过程中对…...

java队列--数据结构

文章目录 前言本文源代码网址&#xff1a;https://gitee.com/zfranklin/java/tree/master/dataStructure/src/com/njupt/queue队列的性质数组队列成员变量方法 链表栈成员变量方法 总结 前言 顺序表和链表两种存储方式实现数据结构–队列。 本文源代码网址&#xff1a;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系统&#xff0c;vmware workstation选择的是NAT模式&#xff0c;虚拟机内连不上网络 ping www.baidu.com ping: unknown host www.baidu.com首先检查 DNS 解析服务&#xff1a;在虚拟机中打开命令提示…...

overleaf中的includegraphics设置图片缩放,居中显示

overleaf中的includegraphics设置图片缩放,居中显示 \includegraphics[width=0.5\textwidth]{example.jpg} \centering 在使用 \includegraphics 命令插入图片时,可以通过设置其参数来缩小图片的显示尺寸,以下是几种常见的方法: 设置宽度或高度 按比例缩小宽度:可以使用…...

Ubuntu系统下交叉编译openssl

一、参考资料 OpenSSL&&libcurl库的交叉编译 - hesetone - 博客园 二、准备工作 1. 编译环境 宿主机&#xff1a;Ubuntu 20.04.6 LTSHost&#xff1a;ARM32位交叉编译器&#xff1a;arm-linux-gnueabihf-gcc-11.1.0 2. 设置交叉编译工具链 在交叉编译之前&#x…...

基于大模型的 UI 自动化系统

基于大模型的 UI 自动化系统 下面是一个完整的 Python 系统,利用大模型实现智能 UI 自动化,结合计算机视觉和自然语言处理技术,实现"看屏操作"的能力。 系统架构设计 #mermaid-svg-2gn2GRvh5WCP2ktF {font-family:"trebuchet ms",verdana,arial,sans-…...

CTF show Web 红包题第六弹

提示 1.不是SQL注入 2.需要找关键源码 思路 进入页面发现是一个登录框&#xff0c;很难让人不联想到SQL注入&#xff0c;但提示都说了不是SQL注入&#xff0c;所以就不往这方面想了 ​ 先查看一下网页源码&#xff0c;发现一段JavaScript代码&#xff0c;有一个关键类ctfs…...

【Java学习笔记】Arrays类

Arrays 类 1. 导入包&#xff1a;import java.util.Arrays 2. 常用方法一览表 方法描述Arrays.toString()返回数组的字符串形式Arrays.sort()排序&#xff08;自然排序和定制排序&#xff09;Arrays.binarySearch()通过二分搜索法进行查找&#xff08;前提&#xff1a;数组是…...

相机Camera日志实例分析之二:相机Camx【专业模式开启直方图拍照】单帧流程日志详解

【关注我&#xff0c;后续持续新增专题博文&#xff0c;谢谢&#xff01;&#xff01;&#xff01;】 上一篇我们讲了&#xff1a; 这一篇我们开始讲&#xff1a; 目录 一、场景操作步骤 二、日志基础关键字分级如下 三、场景日志如下&#xff1a; 一、场景操作步骤 操作步…...

渲染学进阶内容——模型

最近在写模组的时候发现渲染器里面离不开模型的定义,在渲染的第二篇文章中简单的讲解了一下关于模型部分的内容,其实不管是方块还是方块实体,都离不开模型的内容 🧱 一、CubeListBuilder 功能解析 CubeListBuilder 是 Minecraft Java 版模型系统的核心构建器,用于动态创…...

JUC笔记(上)-复习 涉及死锁 volatile synchronized CAS 原子操作

一、上下文切换 即使单核CPU也可以进行多线程执行代码&#xff0c;CPU会给每个线程分配CPU时间片来实现这个机制。时间片非常短&#xff0c;所以CPU会不断地切换线程执行&#xff0c;从而让我们感觉多个线程是同时执行的。时间片一般是十几毫秒(ms)。通过时间片分配算法执行。…...

QT: `long long` 类型转换为 `QString` 2025.6.5

在 Qt 中&#xff0c;将 long long 类型转换为 QString 可以通过以下两种常用方法实现&#xff1a; 方法 1&#xff1a;使用 QString::number() 直接调用 QString 的静态方法 number()&#xff0c;将数值转换为字符串&#xff1a; long long value 1234567890123456789LL; …...

Java多线程实现之Thread类深度解析

Java多线程实现之Thread类深度解析 一、多线程基础概念1.1 什么是线程1.2 多线程的优势1.3 Java多线程模型 二、Thread类的基本结构与构造函数2.1 Thread类的继承关系2.2 构造函数 三、创建和启动线程3.1 继承Thread类创建线程3.2 实现Runnable接口创建线程 四、Thread类的核心…...

laravel8+vue3.0+element-plus搭建方法

创建 laravel8 项目 composer create-project --prefer-dist laravel/laravel laravel8 8.* 安装 laravel/ui composer require laravel/ui 修改 package.json 文件 "devDependencies": {"vue/compiler-sfc": "^3.0.7","axios": …...