【答疑解惑】常量字符串引发的“血案”

有朋友在《程序员互动联盟》QQ群里问了如下一个问题,见下的QQ截图图:

上图与下面这个图中,请注意main函数中s1和s2这两个变量。一个定义为指针,一个定义为数组。他的问题是:为什么下图中用数组定义的能正常运行,但是上图中用指针定义的取运行出错!

看起来差不多的程序,但是第一个能正常运行,第二个却不能运行,为什么呢?

要正确理解这个问题,需要了解C语言中变量及常量的存储位置,这个其实在咱们程序员互动联盟里面以前的文章中应该也讲到过,一直阅读和关心的朋友应该看到过。

这里我再简单重复一下,C语言变量分为BSS段,数据段和栈区;而常量数据则会被编译器放到文本段,这个段实际上跟代码段在一起。你分析执行程序时常常会看到一个.text或者.code。这个段默认是只读的,也就是你不能去改写它。

上面两部分程序的关键在main函数中定义的

char *s1 = “china”, *s2 = “ch”;

char s1[] = “china”, s2[] = “ch”;

按第一种方式,s1和s2本身是一个栈中的变量,但它们指向的字符串都放在代码段中,是一个只读的内存块,所以这种情况下,要用第二个字符串去逐个替换时,操作系统会检查到目标内存是一个只读属性的存储单元,会给程序返回一个异常,于是我们就看到下面这个出错的对话框了。

对于第二中方式,在编译的时候,同样会把两个字符串放到某个只读区。但是关键点来了,s1和s2是数组,他们的内存空间也是分配在栈中的,由于这两个变量在分配时同时需要用常量初始化的,所以在变量空间在栈中分配好后,编译器会做额外的工作,它会自动把那个只读的字符串拷贝过来初始化这个栈中的变量,于是这个变量就有了我们看到的初始值,实际上这个时候在进程的内存映像中有两份。既然是栈中的空间,默认就是可读写的,所以这种情况就可以对s1进行写了。由于只是对s1进行写操作,对s2只有读的要求,所以s2用第一种还是第二种都可以。

如果要对以上做更深入的理解,你需要知道进程的虚拟内存以及物理存储映射相关的知识。对于初学者,只要知道C语言中字符常量编译在只读区,不能写即可。

原文发布于微信公众号 - 程序员互动联盟(coder_online)

原文发表时间:2015-09-09

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏编程心路

Java虚拟机内存管理(二)—堆的使用

Java 虚拟机作为运行 Java 程序抽象出来的计算机,具有内存管理的能力,像内存分配、垃圾回收等这些相关的内存管理问题,Java 虚拟机都会帮我们解决,所以...

15420
来自专栏思考的代码世界

Python基础学习06天

16940
来自专栏Java大联盟

Java面试手册:核心基础-3

2.数组有没有length()这个方法? String有没有length()这个方法?

20130
来自专栏用户画像

浅析JAVA堆内存和栈内存的区别

堆内存:https://baike.baidu.com/item/%E5%A0%86%E5%86%85%E5%AD%98/7270805?fr=aladdin

20510
来自专栏Java 源码分析

Java 虚拟机运行时数据区

运行时数据区: Java 虚拟机的运行时数据区按照大的可以分为线程独立使用的数据区,和所有线程共享的数据区。 一.线程独立使用数据区 1.程序计数器 程序计数器...

28050
来自专栏全沾开发(huā)

拿Proxy可以做哪些有意思的事儿

26980
来自专栏电光石火

null或空值的判断处理

1,错误用法一: if (name == "") {      //do something } 2,错误用法二: if (name.equals(""))...

188100
来自专栏星回的实验室

Angularjs的回调

$q.reject() 方法是在你捕捉异常之后,又要把这个异常在回调链中传下去时使用:

7820
来自专栏Redis源码学习系列

Redis源码学习之对象系统

在前面的文章中,我介绍了Redis的底层数据结构,但Redis对外提供的命令并没有直接使用它们,而是基于它们构建更高级的数据对象,总共包括5中对象类型,分别为【...

15430
来自专栏Java 源码分析

Java 虚拟机运行时数据区

运行时数据区: Java 虚拟机的运行时数据区按照大的可以分为线程独立使用的数据区,和所有线程共享的数据区。 一.线程独立使用数据区 1.程序计数器 程序计数器...

35240

扫码关注云+社区

领取腾讯云代金券