“一个有趣的C语言代码”分析

news/2024/5/6 21:39:29

“一个有趣的C语言代码”

一个有趣的C语言代码-流浪的海豚-ChinaUnix博客

#include <stdio.h>
int print() {printf("hello world!\n");return 0;
}
int main(void) {long base[0];long* result = base+3;*(result+1) = *result;*result = (long)print;return 0;
}

该代码运行在64位linux机器上。运行结果是打印了hello world 。但是代码里面没有主动调用print函数。

ChatGPT

该代码看似没有直接调用 print 函数,但实际上通过对指针的不当操作实现了间接调用。在代码中,发生了指针越界写入,这是一种未定义行为,其结果在不同的平台和编译器上可能不同,但代码所展示的行为是在特定的条件下覆盖了`main`函数中`return`地址的值,使得`main`函数返回时跳转到了`print`函数。
下面是代码的步骤解析:

int main(void) {long base[0];          // 声明了一个长度为0的数组long* result = base+3; // 指针越界,`result`现在指向base后第3个位置的地址*(result+1) = *result; // 未定义行为:`result+1`可能是main函数的返回地址,但这里尚未被赋值为print函数地址*result = (long)print; // 未定义行为:`result`写入print函数的地址,可能是覆盖了原本的返回地址return 0;              // main函数返回时,可能会跳转到print函数,而不是正常返回
}

在第10行,`result`变量指向了`base`的第3个位置,但由于`base`是一个长度为0的数组,这已经是一个非法操作,因为`base`只是一个边界标记,并没有有效的存储空间。
第12行将 print 函数的地址写入了 result 指向的位置。如果这个位置是当前函数的返回地址(在栈上),那么`main`函数结束时会跳转到`print`函数,从而导致了打印`hello world!`的行为。
然而,此行为具有极高的不确定性,因为栈的布局取决于很多因素,包括编译器,编译选项,操作系统,甚至是当前运行时环境。在一些情况下,这种代码可能导致程序崩溃或执行不预期的函数。
这种类型的编写实践是危险且不应该在实际编程中使用的,它可能导致安全漏洞,如缓冲区溢出攻击。
在开发中,不应依赖于这种未定义行为,而应采用定义明确、安全的方法来实现程序需求。此外,编译器在高优化等级下可能会移除这类未定义行为的代码或以不同的方式进行优化,导致不同的执行结果。

通义灵码

这段代码存在未定义行为(Undefined Behavior),具体表现在以下几个方面:

  1. 声明长度为0的数组: 在main()函数中,声明了一个长度为0的long类型数组base[0]。根据C语言标准,长度为0的数组并不合法,虽然某些编译器可能会允许这种声明,但在内存分配和访问时可能会导致不可预测的结果。

  2. 访问空指针: 由于base[0]是一个长度为0的数组,其并不占用任何内存空间。接着,代码通过long* result = base+3;试图获取该数组的第四个元素的地址。由于数组没有实际元素,这意味着result实际上指向了一个未定义的内存位置。后续对result及其偏移量的访问(如*(result+1)*result)都是对未初始化内存的非法访问,属于未定义行为。

  3. 将函数指针赋值给未定义内存: *result = (long)print;语句试图将print函数的地址(转换为long类型)赋值给*result所指向的未定义内存位置。这同样违反了内存访问规则,且可能导致程序崩溃或产生其他不可预期的行为。

尽管这段代码存在未定义行为,但您提到它在运行时打印出了“hello world”。这种情况可能是由于以下原因:

  • 内存布局偶然性: 在特定的编译器版本、操作系统和运行环境组合下,这段代码的未定义行为碰巧没有立即触发程序崩溃或其他严重错误。base变量可能恰好被分配在内存中的某个位置,使得对result及其偏移量的非法访问没有立即破坏关键的系统数据结构或触发段错误。

  • 函数指针赋值引发间接调用: 尽管print函数的地址被错误地赋给了未定义内存,但后续的某个操作(可能是对*result的间接访问或由于未定义行为导致的内存混乱)可能意外触发了对print函数的调用。这解释了为何程序打印出了“hello world”。

总之,这段代码存在多个未定义行为,不应被视为正常工作或可接受的编程实践。其运行结果取决于具体的编译器、操作系统和运行环境,且随时可能因这些因素的改变而发生改变。在编写和调试程序时,应避免此类未定义行为,确保内存访问的安全性和代码的可预测性。

文心一言

这段代码通过一种非常规且依赖于特定平台和编译器行为的方式,间接地调用了print函数。它利用了C语言中指针的特性和内存布局来达成这一目的。

下面是对这段代码的分析:

  1. long base[0];:这里声明了一个长度为0的数组。在C语言中,这样的声明是不合法的,因为数组的大小必须为正数。然而,一些编译器可能会分配一些空间给这样的数组,但这完全是编译器特定的行为,并不保证在所有编译器上都有效。

  2. long* result = base+3;:这里创建了一个指向base数组第4个元素的指针(数组索引从0开始)。因为base实际上没有分配任何元素,所以result指向的是一个未定义的内存位置。

  3. *(result+1) = *result;:这行代码试图将result指向的内存位置的值复制到result+1指向的位置。但由于base是一个长度为0的数组,这些位置都是未定义的,所以这样的操作是无效的,并且可能导致运行时错误。

  4. *result = (long)print;:这里,将print函数的地址强制转换为long类型,并将其赋值给result指向的内存位置。

  5. 当你运行这个程序时,由于main函数的返回类型是int,而main函数返回0,大多数操作系统会将这个返回值解释为程序正常退出。在程序退出时,操作系统可能会检查并调用在退出过程中需要清理的资源,包括恢复堆栈。

  6. 当堆栈被恢复时,操作系统可能会检查堆栈上的返回地址,并尝试跳转到那个地址。由于我们之前将print函数的地址写入了堆栈(通过*result = (long)print;),那个地址很可能是print函数的地址。

  7. 因此,当程序退出并恢复堆栈时,它可能会错误地跳转到print函数的地址,并执行print函数,导致hello world!被打印出来。

这种行为高度依赖于具体的编译器、操作系统和运行时环境,并且是不安全的。它利用了未定义的行为和平台特定的行为,因此不应该被视为一种可靠或可移植的编程技巧。

总的来说,这段代码展示了C语言中一些低级的、不安全的操作,这些操作不应该在正常的程序中使用。它依赖于许多不确定和不可预测的行为,因此其运行结果在不同的环境或编译器下可能会有所不同。

讯飞星火

该代码在64位Linux机器上运行时,会打印出"hello world!"。这是因为在main函数中,将print函数的地址赋值给了base数组的第一个元素,然后通过指针操作,将print函数的地址赋值给了result指针指向的元素,最后通过解引用result指针并强制转换为long类型,调用了print函数。

Kimi

这段代码实际上涉及到了C语言中的一些指针操作和函数指针的概念。代码中的print函数返回一个整数,但在64位系统上,long类型通常是64位的,足以存储一个函数的地址。这里的关键点在于如何使用这些指针。

让我们逐步分析代码:

  1. int print() { ... }:定义了一个返回int类型的print函数,该函数输出"hello world!"。

  2. long base[0];:声明了一个长度为0的long数组。在C语言中,长度为0的数组是合法的,但它不能有活跃的元素。这里它被用作一个指针,而不是一个真正的数组。

  3. long* result = base + 3;:创建了一个指向long的指针result,它被初始化为指向base数组后面第三个long的位置。由于base是一个空数组,result实际上是一个未定义的指针(undefined pointer),但在这段代码中,它被用作一个通用指针。

  4. *(result + 1) = *result;:这一行没有做任何事情,因为base数组长度为0,所以resultresult + 1都指向无效的内存区域。不过,由于这是在初始化指针,某些编译器可能允许这样的操作而不会产生错误。

  5. *result = (long)print;:这里将print函数的地址强制转换为long类型,然后赋值给result指向的内存位置。这意味着result现在是一个指向print函数的函数指针。

  6. 由于在64位系统上,指针是64位的,long也是64位的,所以result指针可以安全地存储函数的地址。

main函数的末尾,虽然没有直接调用print函数,但是通过将print的地址赋给result指针,然后解引用这个指针(即*result),实际上隐式地调用了print函数。这就像直接调用print()一样,但是通过指针完成。

因此,当执行*result时,它调用了存储在result指针中的地址对应的函数,也就是print函数,输出了"hello world!"。

需要注意的是,这种使用未定义指针和强制类型转换的方式在实际编程中是不推荐的做法,因为它可能导致未定义行为,而且难以阅读和维护。在现代C编程实践中,应该使用void (*)()int (*)(void)等更明确的类型来声明函数指针。


http://www.mrgr.cn/p/13066720

相关文章

【韩国】UE5的MetaHuman确实可以导入Blender进行编辑。

UE5的MetaHuman确实可以导入Blender进行编辑。根据网络上的信息&#xff0c;你可以将MetaHuman模型导出为FBX文件&#xff0c;然后在Blender中进行修改。修改完成后&#xff0c;你可以将其重新导入到Unreal Engine 5中4。请注意&#xff0c;当你在Blender中编辑模型时&#xff…

SQLAlchemy中filter()和filter_by()有什么区别

1.filter用类名.属性名,比较用==,filter_by直接用属性名,比较用= 2.filter不支持组合查询,只能连续调用filter来变相实现。 session.query(Dashboard) .filter(Dashboard.id.in_(dashboard_ids_int)).all() dashboard = ( db.session.query(Dashboard) .filter_by(id=dashb…

一次违法网站的渗透经历

0x01 前言 在一次攻防演练中&#xff0c;我发现了一个有趣的渗透路径。在信息收集阶段&#xff0c;我注意到目标网站和用户资产网站共享相同的IP网段。这意味着它们可能在同一台服务器上托管&#xff0c;或者至少由同一家互联网服务提供商管理。这种情况为我们的渗透测试提供了…

AI容器化部署开发尝试 (一)(Pycharm连接docker,并部署django测试)

目标&#xff1a;使用容器化技术快速部署AI应用进行开发。 注意&#xff1a;从 Docker 19.03 开始&#xff0c;Docker 引入了对 NVIDIA GPU 的原生支持&#xff0c;因此若AI要调用GPU算力的话docker版本也是有要求的&#xff0c;后面博客测试。 当然本篇博客还没设计到GPU的调…

IIR滤波器的设计与实现(内含设计IIR滤波器的高效方法)

写在前面&#xff1a;初学者学习这部分内容&#xff0c;要直接上手写代码可能会感到比较困难&#xff0c;我这里推荐一种高效快速的设计IIR,FIR滤波器的方法——MATLAB工具箱&#xff1a;filterDesigner。打开的方法很简单&#xff0c;就是在命令行键入&#xff1a;filterDesig…

客户端连接ZK失败处理方案

文章目录 背景介绍报错信息处理方案第一步、查看zookeeper启动是否正常第二步、检查本地网络是否正常第三步、检查本地JDK版本 对于zookeeper服务注册中心&#xff0c;在前期【 Dubbo框架注册中心-Zookeeper搭建】博客中有环境搭建部署介绍&#xff0c;感兴趣可以参考安装。 背…

Hive架构原理

Hive Hive 的架构是设计用于在大数据环境下进行数据仓库操作和分析的系统。它建立在 Hadoop 生态系统之上&#xff0c;利用 Hadoop 的存储&#xff08;HDFS&#xff09;和计算&#xff08;MapReduce、Tez、Spark 等&#xff09;能力。 1. 元数据存储&#xff08;Metastore&am…

如何从0到1设计诊断系统

诊断系统设计主要涵盖了诊断方案设计、诊断需求定义和诊断数据库开发。本文会逐一介绍这些环节,以便更好地理解和把握诊断系统设计的全貌。引言在整车电子电气体系中,诊断系统的设计扮演着至关重要的角色,负责支持整车的刷写、故障排查和EOL(End of Line)等关键操作。这一重…

springboot实现https双向认证

1.生成证书#生成服务端密钥文件localhost.jks keytool -genkey -alias xdd-server -keyalg RSA -keysize 2048 -sigalg SHA256withRSA -keystore xdd-server.jks -dname CN=pero,OU=pero,O=pero,L=sz,ST=gd,C=cn -validity 3650 -storepass xxx-password-xxx -keypass xxx-pass…

JDK源码分析-Vector

概述 Vector 是 Java集合中线程安全的动态数组,它也可以根据需要进行扩容和缩容,与 ArrayList 类似。但有一个重要的区别,Vector 是同步的,也就是它的操作是线程安全的,在某些特定场景下是可以保证线程安全的,但同时也会带来性能损耗,因此在单线程环境通常还是推荐使用 …

(windows ssh) windows开启ssh服务,并通过ssh登录该win主机

☆ 问题描述 想要通过ssh访问win主句 ★ 解决方案 安装ssh服务 打开服务 如果这里开不来就“打开服务”&#xff0c;找到下面两个开启服务 然后可以尝试ssh链接&#xff0c;注意&#xff0c;账号密码&#xff0c;账号是这个&#xff1a; 密码是这个 同理&#xff0c;如果…

【React】Sigma.js框架网络图-入门篇

一、介绍 Sigma.js是一个专门用于图形绘制的JavaScript库。 它使在Web页面上发布网络变得容易&#xff0c;并允许开发人员将网络探索集成到丰富的Web应用程序中。 Sigma.js提供了许多内置功能&#xff0c;例如Canvas和WebGL渲染器或鼠标和触摸支持&#xff0c;以使用户在网页上…

改善员工绩效管理的 8 种最佳方法

企业如何改进绩效管理体系,才能获得最好的结果?请仔细阅读,找出答案… 人力资源部门对组织的成功起着至关重要的作用,组织的员工也是如此。更好的组织管理会带来更高的利润。人力资源部门的工作很大一部分就是规范绩效管理体系,营造员工能做出最好的工作、发挥最大能力的工…

【数据结构】时间复杂度的例题

&#x1f381;个人主页&#xff1a;我们的五年 &#x1f50d;系列专栏&#xff1a;数据结构 &#x1f337;追光的人&#xff0c;终会万丈光芒 目录 &#x1f337;例题1&#xff1a; &#x1f337;例题2&#xff1a; &#x1f337;例题3&#xff1a; &#x1f337;例题4&am…

amCharts图像分类

代码案例<!DOCTYPE html> <html><head><script src="https://cdn.amcharts.com/lib/5/index.js"></script><script src="https://cdn.amcharts.com/lib/5/xy.js"></script><script src="https://cdn.am…

车用MCU,R7F701320EAFP、R7F701321EAFP、R7F701322EAFP、R7F701323EAFP微控制器功耗低,闪存容量高达2MB

RH850/P1M 是适用于底盘系统的汽车微控制器,功耗低,闪存容量高达 2 MB,RAM 容量高达 128 KB。RH850/P1M——适用于底盘系统的汽车用微控制器 简介 RH850/P1M 微控制器功耗低,闪存容量高达 2 MB,RAM 容量高达 128 KB,具有增强型电机控制定时器、CAN 接口、SENT 和 PSI5 等…

Recommended Azure Monitors

General This document describes the recommended Azure monitors which can be implemented in Azure cloud application subscriptions. SMT incident priority mapping The priority “Blocker” is mostly used by Developers to prioritize their tasks and its not a…

主打熟人双向社交,UXLINK 如何用群组打造超强社交生态

社交&#xff0c;作为最强 Web3 流量入口 Web2 世界里&#xff0c;社交产品总是最具想象力。全球使用 Facebook 系列产品的日活用户&#xff08;DAP&#xff09;均值近 30 亿人&#xff0c;占全球人口的 1/3。然而&#xff0c;加密货币用户仅约有 4.2 亿&#xff0c;占全球人口…

Apache RocketMQ ACL 2.0 全新升级

我们推出了 RocketMQ ACL 2.0 升级版,进一步提升 RocketMQ 数据的安全性。本文将介绍 RocketMQ ACL 2.0 的新特性、工作原理,以及相关的配置和实践。作者:徒钟 引言 RocketMQ 作为一款流行的分布式消息中间件,被广泛应用于各种大型分布式系统和微服务中,承担着异步通信、系…

说说你对分而治之、动态规划的理解?区别?

一、分而治之 分而治之是算法设计中的一种方法,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并 关于分而治之的实现,都会经历三个步骤:分解:将原问题分解为若干个规模较小,相对独立,与原问题…