C语言之修改常量
前言:指针!菜鸟的终点,高手的起点。漫谈一些进阶之路上的趣事;记录一些语言本身的特性以及思想,没有STL,也没有API!
0x01: 程序内存中的存储划分
对于程序在内存中是如何分布的,网上有多个解释的版本(解释为3、4、5、6个区的都有),这里我也不赘述了,反正该有的都有,只是看个人怎么理解
建议自己搜来看看温习一下(主要是栈区、常量区、代码段),看懵了就不要说我描述有问题了......
0x02: 变量与常量
程序的运行过程(屏蔽一些较为底层的东西):
① 将物理内存(磁盘等存储介质)中的程序文件装入运行内存中,程序中的内存指的是运行内存
② CPU从内存中的指定位置读取到指令加以运行,这里的指令最终都是对于内存的操作
程序中定义的操作存储于内存 - 代码段,操作指C代码指令编译的结果,例如赋值操作、比较运算等;CPU读取指令的位置
程序中定义的局部变量存储于内存 - 栈区,这是我们最常使用的存储区域;这些变量在同一作用范围内时我们可以随意改变其值
程序中定义的常量存储于内存 - 数据区,数据区中全局变量、静态变量、常量的存储区不同,我们通常使用 'const' 定义的常量是存储在常量区的,常量区的数据根据规定是不可改变的
思考:程序加载到内存中的绝对位置是由操作系统决定的,程序可以加载到的内存(除系统保留区)也是平等的,为什么存储在栈区的变量可以改变而存储在代码区和常量区的数据不可改变;理论上来说该程序可以操作的内存(也就是系统加载该程序时分配的内存地址范围)都是可以被改变的,所以这里可以推测为程序做了权限的限制
0x03: 指针操作的本质
指针操作是可以直接作用于内存的,使用指针操作时只有两个限制,一个是定义指针时规定的对于变量本身的限制,一个是该程序的寻址空间限制;在某些情况下这两个限制都可以突破,这里不作论述
指针的强大之处在于它能修改所有能寻址到的内存中的值;对应程序在内存中的分配,理论上可以使用指针操作栈区堆区(常用),那么同样可以操作数据区和代码区;语言限制中不允许修改操作的区域为代码区和数据区中的常量区,这里我们可以将指针指向这两个区域,这样就能达到修改代码和常量的目的
0x04: 通过指针操作常量区
代码示例:
const int a=;
int *pa=(int*)&a;
*pa=;
printf("*pa=%d,a=%d",*pa,a); /*输出:
*pa=99,a=10
*/
常量修改
示例中第二行必需使用强转,C中认为 'const' 是更加广泛的类型限制
输出结果是不是有点奇怪?理论上来说定义的 'const' 常量存储的常量区也在内存中,为什么 'a' 和 '*pa' 的值不一样呢?难道说使用这两个名的时候不是寻址的同一块内存?或者是程序寻址的时候使用相同的地址实际地址是不同的区域(a在常量区,对pa赋值时在栈区生成了新的*pa内存)?
我们再深入看看:
const int a=;
int *pa=(int*)&a;
printf("*pa=%d,a=%d,pa=%p,&a=%p\n",*pa,a,pa,&a);
*pa=;
printf("*pa=%d,a=%d,pa=%p,&a=%p\n",*pa,a,pa,&a); /*输出:
*pa=10,a=10,pa=0019FF3C,&a=0019FF3C
*pa=99,a=10,pa=0019FF3C,&a=0019FF3C
*/
常量地址
这里分别输出了赋值之前两者的值和地址、赋值之后两者的值和地址,这里我们可以知道地址是相同的,但值就是不同???我去 哪有这么怪的事,同一块地址的值同一时间取怎么就不同了?
这时候我们使用 'F10' 单步调试大法进行变量跟踪(VC++6.0),打开变量池、内存,跟踪过程(需要一点点的调试能力):
① 第一行定义 'const' 变量,查看变量 'a' 的值(=10)、查看 'a' 的地址 '&a' (=0x0019FF3C)
② 第二行定义 'int' 指针指向 'a',查看 '*pa' 的值(=10)、查看 'pa' 的值(=0x0019FF3C)
③ 运行并查看输出,没问题
④ 使用 '*pa' 对这块内存赋值,查看 'a'、'&a'、'*pa'、'pa' 的值,其中 'a' 的值和 '*pa' 的值变成了 '99',正常
⑤ 运行并查看输出,得到输出中的最后一行 '*pa=99,a=10,pa=0019FF3C,&a=0019FF3C'
???啥意思???④中得到了 'a' 的值明明为 '99',⑤这输出咋回事儿啊?
再使用内存view查看地址为 '0x0019FF3C' 地址内的值,为 '63 00 00 00',小端存储的十六进制,63H==99D;可以得到的结论为:使用这两个名进行寻址的是同一块内存,同一程序中寻址方式唯一
问题就在于这一块内存的原值在赋值之后已经被新的值覆盖掉了,读取到的 'a' 的值是从哪来的,'a' 的值一定在内存中的某个位置
接下来再进一步跟踪程序运行过程,查询程序中间步骤,单步调试汇编语句,打开寄存器、汇编文件(需要再多一点点调试能力,只解释相关语句):
: #include <stdio.h>
:
: void main(void)
: {
push ebp
mov ebp,esp
sub esp,48h
push ebx
push esi
push edi
lea edi,[ebp-48h]
0040101C mov ecx,12h
mov eax,0CCCCCCCCh
rep stos dword ptr [edi]
: const int a=;
mov dword ptr [ebp-],0Ah
: int *pa=(int*)&a;
0040102F lea eax,[ebp-]
mov dword ptr [ebp-],eax
: *pa=;
mov ecx,dword ptr [ebp-]
mov dword ptr [ecx],63h
: printf("*pa=%d,a=%d,pa=%p,&a=%p\n",*pa,a,pa,&a);
0040103E lea edx,[ebp-]
push edx
mov eax,dword ptr [ebp-]
push eax
push 0Ah
mov ecx,dword ptr [ebp-]
0040104B mov edx,dword ptr [ecx]
0040104D push edx
0040104E push offset string "*pa=%d,a=%d,pa=%p,&a=%p\n" (0042201c)
call printf ()
add esp,14h
: }
0040105B pop edi
0040105C pop esi
0040105D pop ebx
0040105E add esp,48h
cmp ebp,esp
call __chkesp ()
mov esp,ebp
0040106A pop ebp
0040106B ret
汇编文件
以上汇编代码中的注释代码为C的源代码,其余汇编语句只做重要点的讲解
① 4-5 行之间是做初始化、入栈一类的操作,略过
② 5-6 将 0xA 存到 'a'
③ 6-7 取 'a' 的地址存到 'pa'
④ 7-8 取 'pa' 值对应的地址,存入 0x63
⑤ 8-9 输出:将变量压栈、字符串压栈调用 'printf()' 库函数
⑥ 9-最后 出栈、释放空间、返回等操作
其中 ① ⑥ 我也不太懂,②-④步是比较简单的操作,关键在第⑤步
可以看到 'printf()' 函数的调用过程:依次使用 'push' 压入4个需要串化的参数、压入原字符串,最后 'call printf()',压入参数的顺序为从右至左:
执行第一个 'push' 时查得 'edx' 值为 0x0019FF3C,是 '&a' 的值
第二个 'push' 时 'eax' 值为 0x0019FF3C,是 'pa' 的值
第三个 'push' 的值为 0x0A,是 'a' 的值
第四个 'push' 时 'edx' 值为 0x63,是 '*pa' 的值
问题就在于第三个 'push' 目标直接为值 0x0A 而不是取 '&a' 这个地址内的值,根据之前的推断即使是常量也需要从其存储位置取值,而实际情况却是在编译时就进行了类似 '#define' 之类的直接替换......
章结:
一个无聊的实验,如何修改常量;得出的结论:使用指针操作常量区是没有任何问题的,但有时即使修改了常量区的值也对运行结果没有影响,编译器会优化在使用常量时不去常量的存储位置取值,而是编译阶段直接将值写入到代码区
另:即使写入到代码区的值也可以修改,通过某种神奇的方法找到编译后代码的位置,将逻辑修改为从内存寻值;或者暴力点内嵌汇编......
写在最后:
这是一个困扰了我三年的问题(有点丢人),初学C时就碰到了这个问题,当时问老师说没遇到过我这么用的,就没有和这个问题刚到底(也不会这些技术);技术的话可能还是有一些地方描述得有问题,望大佬不吝赐教,同时也希望这篇文章中的东西能对同学们有哪怕一丝用处
C语言之修改常量的更多相关文章
- C/C++修改常量的值
C/C++中常量修饰const可以用来保证一些确定的量不会被一不小心改变,比如PI,一直是3.14159...... 但是不排除有时候也会需要修改常量的值,通过直接修改是不能达到目的. 比如: #in ...
- Swift语言指南(一)--语言基础之常量和变量
原文:Swift语言指南(一)--语言基础之常量和变量 Swift 是开发 iOS 及 OS X 应用的一门新编程语言,然而,它的开发体验与 C 或 Objective-C 有很多相似之处. Swif ...
- Go语言变量和常量
一.变量相关 1.变量声明 C# : int a; Go : var a int; 需要在前面加一个var关键字,后面定义类型 可以使用 var( a int; b string;)减少var 2.变 ...
- Go 语言基础——变量常量的定义
go语言不支持隐式类型转换,别名和原有类型也不能进行隐式类型转换 go语言不支持隐式转换 变量 变量声明 var v1 int var v2 string var v3 [10]int // 数组 v ...
- C语言中字符串常量到底存在哪了?
常量存储总结局部变量.静态局部变量.全局变量.全局静态变量.字符串常量以及动态申请的内存区 1.局部变量存储在栈中2.全局变量.静态变量(全局和局部静态变量)存储在静态存储区3.new申请的内存是在堆 ...
- C语言实现修改文本文件中的特定行
最近由于项目需要实现修改文件的功能,所以,博主认真查阅了一些资料,但是,很遗憾,并没有太多的收获. 好的,首先我先叙述下功能要求: 其实很简单,就是Shell中sed命令的C语言实现,实现定 ...
- [C语言]变量VS常量
-------------------------------------------------------------------------------------------- 1. 固定不变 ...
- Linux语言设置修改乱码
1.system-config-language 命令语言改成英文.(安装yum install system-config-language) 如何系统安装后,使用的语言不是自己想要的.但是在图形 ...
- Go语言中的常量
1 概述 常量,一经定义不可更改的量.功能角度看,当出现不需要被更改的数据时,应该使用常量进行存储,例如圆周率.从语法的角度看,使用常量可以保证数据,在整个运行期间内,不会被更改.例如当前处理器的架构 ...
随机推荐
- 第八篇 Flask中的蓝图
随着业务代码的增加,将所有代码都放在单个程序文件中,是非常不合适的.这不仅会让代码阅读变得困难,而且会给后期维护带来麻烦.如下示例:我们在一个文件中写入多个路由,这会使代码维护变得困难. 如图所示,如 ...
- 基于Mustache实现sql拼接
目录 一.前言 二.Mustache语法 三.Mustache拼接sql 一.前言 Mustache语法是一种模板语法,它可以帮我们拼接我们想要的东西.入职新公司,而项目里的sql语句就是用Musta ...
- Office中国在这个开个博客
Office中国在这个开个博客,先来show一下我的网站 Office中国/Access中国 http://www.office-cn.net Office中国百科: http://baike. ...
- li列表循环滚动的简单方法,无需插件,简单方法搞定
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- Vue学习笔记(五)——配置开发环境及初建项目
前言 在上一篇中,我们通过初步的认识,简单了解 Vue 生命周期的八个阶段,以及可以应用在之后的开发中,针对不同的阶段的钩子采取不同的操作,更好的实现我们的业务代码,处理更加复杂的业务逻辑. 而在这一 ...
- 拎壶冲冲冲专业砸各种培训机构饭碗篇----python自学(一)
本人一直从事运维工程师,热爱运维,所以从自学的角度站我还是以python运维为主. 一.python自学,当然少不了从hello world开始,话不多说,直接上手练习 1.这个可以学会 print( ...
- CF741D Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths——dsu on tree
题目描述 一棵根为1 的树,每条边上有一个字符(a-v共22种). 一条简单路径被称为Dokhtar-kosh当且仅当路径上的字符经过重新排序后可以变成一个回文串. 求每个子树中最长的Dokhtar- ...
- python——namedtuple
namedtuple()概念理解分享 我们都知道元组tuple的概念,tuple是一个定义之后就不能够更改的可迭代对象,namedtuple作为tuple的兄弟具有同样的属性,一旦定义就不可以更改.但 ...
- linux写系统服务的方法
linux写系统服务的方法 2.1 首先编写demo程序:hello.c<pre>#include <stdio.h> # chkconfig: 2345 10 90 main ...
- 我们碰到了大麻烦,一个新来的传教士惹恼了上帝,上帝很愤怒,要求我们把圣经(bbe.txt)背熟,直至他说哪个单词,我们就要飞快的回答出这个单词在第几行第几个单词位置。听说你是个优秀的程序员,那么髟助我们完成这个不可能的任务吧
编程任务:1.我们碰到了大麻烦,一个新来的传教士惹恼了上帝,上帝很愤怒,要求我们把圣经(bbe.txt)背熟,直至他说哪个单词,我们就要飞快的回答出这个单词在第几行第几个单词位置.听说你是个优秀的程序 ...