摘要: 通过几段小程序深入分析了C语言中字面字符串(literal string)的特点以及正确的使用方式。
如果对C语言的字面字符串(literal string)缺乏足够的了解,编程时不注意它的特点,就可能会遇到一些略显奇怪的状况。本文对下面这段简单的代码加以几个简单的变形,再分别分析它们的输出,最后总结出字面字符串的特点和编程时需要注意的地方。
#include <stdio.h>
int main() {
printf("Hello!\n"); //Hello!
return 0;
}
本文出现的所有代码的测试环境均为运行32-bit Debian Linux操作系统的Raspberry Pi 3
变形#1,声明一个局部字符类型指针指向字面字符串
#include <stdio.h>
int main() {
char *s = "Hello!\n";
printf(s); //Hello!
return 0;
}
依然输出Hello!, 符合预期。
变形#2, 修改字符串的第一个字符为'B'
#include <stdio.h>
int main() {
char *s = "Hello!\n";
*s = 'B'; //crash here
printf(s);
return 0;
}
运行到*s = 'B'这句时进程异常退出, 错误信息为Segmentation fault,看上去有些奇怪,但我们先将这个问题放在一边,继续看后面几种变形。
变形#3, 在一个全部变量和一个局部变量中定义两个完全一样的字面字符串,观察这两个字符串所在的位置
#include <stdio.h>
char *gs = "Hello!\n";
int main() {
char *s = "Hello!\n";
printf("%p,%p\n", gs, s); //0x104dc,0x104dc
return 0;
}
这两个指针的所指向的位置是完全一样的!也就是说,即使代码中定义了多个相同的字面字符串,C编译器实际上也仅生成了一份拷贝。
变形#4, 考察字面字符串所在地址的内存访问权限。
#include <stdio.h>
#include <unistd.h>
char *gs = "Hello!\n";
int main() {
char *s = "Hello!\n";
printf("%p,%p\n", gs, s); //0x10518,0x10518
sleep(100000);
return 0;
}
先让代码#4打印出那两个相同的地址后长时间sleep,再趁它熟睡时通过ps命令查到该进程的pid为27612,然后查看/proc/27612/maps文件就获得了该进程的内存映射信息,其中第一行为
00010000-00011000 r-xp 00000000 b3:07 933808 /home/pi/a.out
这说明从地址0x10000开始的长度为4k的区域(恰好是一个页面的大小)是只读的,如果进程试图写入这块只读区域,就会触发操作系统的内存异常访问保护从而收到SIGSEGV信号并因此退出。
变形#5, 换一种方式来定义字符串。
#include <stdio.h>
#include <unistd.h>
char *gs = "Hello!\n";
int main() {
char s[] = "Hello!\n";
printf("%p,%p\n", gs, s); //0x10538,0x7e9d6360
*s = 'B';
printf(s); //Bello!
sleep(100000);
return 0;
}
将char *s改为char s[]后,编译器会在栈上分配一块和字符串"Hello!\n"同样大小的内存并它将复制进去。采用和变形#4同样的考察办法也能看出指针s的值0x7e9d6360是一个指向栈内存的地址,并且栈内存是可读写的:
7e9b6000-7e9d7000 rwxp 00000000 00:00 0 [stack]
于是,程序正常打印出"Bello!"。显然,还存在一种不使用栈空间而使用堆空间的变形,该变形的实现不在这里描述,留给读者作为练习。
变形#6, 改变内存访问权限。
#include <stdio.h>
#include <sys/mman.h>
char *gs = "Hello!\n";
int main() {
char *s = "Hello!\n";
//align to page boundary then make the page writable
void *page = (void *)((unsigned long)s & 0xffff1000);
if (mprotect(page, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC)) {
perror("mprotect");
}
*s = 'B';
printf(s); //Bello!
printf(gs); //Bello!
return 0;
}
通过调用mprotect()函数将原本只读的内存页设为可写的,我们实现了对字面字符串的直接修改!但是,这种方式的副作用是巨大的,会令所有指向该字符串的指针都被影响,例如,在上面的代码中,通过指针s将'H'改为'B'后指针gs指向的内容也一起被改变了。由于这样的原因,在实际编程中极少会将一个原本只读的代码页改为可写的。相反,在调查某块不应被修改的内存区域被意外改写的bug时,可以将本来可写的内存页面设置为不可写,让有bug的代码由于触发内存访问异常而暴露出来。
http://click.aliyun.com/m/23316/
分享到:
相关推荐
C语言JSON字符串生成代码 C语言JSON字符串生成代码 C语言JSON字符串生成代码 C语言JSON字符串生成代码 C语言JSON字符串生成代码 C语言JSON字符串生成代码 C语言JSON字符串生成代码 C语言JSON字符串生成代码 C语言...
根据给定的信息,本文将详细解释两个用于C语言中字符串截取的方法:`subStringByIndex` 和 `subStringByPointer`。这两个函数都属于自定义实现,它们提供了灵活且实用的功能来帮助开发者轻松地从一个字符串中提取所...
c语言实现字符串替换c语言实现字符串替换c语言实现字符串替换c语言实现字符串替换c语言实现字符串替换c语言实现字符串替换c语言实现字符串替换c语言实现字符串替换c语言实现字符串替换c语言实现字符串替换c语言实现...
纯C语言实现字符串拆分操作,把字符串拆分成字符串数组,然后再输出。 使用sstream方法,作为底层驱动操作实现非常方便。
C语言字符串转换为Python字符串是指将C语言中的字符串数据转换为Python中的字符串对象,以便在Python环境中使用。下面详细介绍了C语言字符串转换为Python字符串的方法。 使用Py_BuildValue()构建字节对象 在Python...
C语言 获取字符串中的数组C语言 获取字符串中的数组C语言 获取字符串中的数组C语言 获取字符串中的数组
c语言字符串逆序输出c语言字符串逆序输出+c语言字符串逆序逐行解释c语言字符串逆序输出+c语言字符串逆序逐行解释c语言字符串逆序输出+c语言字符串逆序逐行解释c语言字符串逆序输出+c语言字符串逆序逐行解释c语言字符...
字符串逆序+c语言字符串逆序输出+c语言字符串逆序逐行解释字符串逆序+c语言字符串逆序输出+c语言字符串逆序逐行解释字符串逆序+c语言字符串逆序输出+c语言字符串逆序逐行解释字符串逆序+c语言字符串逆序输出+c语言...
本资源"《C语言字符串练习(习题+答案).zip》"正是针对这一需求而准备的,它包含了C语言字符串操作的专项练习题和对应的答案,帮助学习者巩固和提升在字符串处理方面的技能。 字符串在C语言中扮演着重要角色,它们...
在C语言中,将浮点数转换为字符串是一项常见的任务,尤其在需要将数值数据输出到文件或屏幕上时。这个过程通常涉及到`printf`函数家族的使用,它们能够按照指定的格式将各种类型的数据转化为可读的字符串。本文将...
根据提供的文件信息,本文将详细解释“C语言删除字符串中指定的所有字符”的实现方法与原理。此代码示例提供了一种高效的方法来移除字符串中的特定字符,并且已经在Windows和Linux环境下进行了测试验证。 ### 一、...
c语言实现字符串分割split(), 实现C语言实现按分隔符来截取字符串
在C语言中,字符串处理是编程中的重要组成部分。C语言本身并不像其他高级语言那样内置了丰富的字符串操作函数,但通过标准库中的`<string.h>`我们可以使用一些基本的字符串函数,如`strcpy`、`strlen`等。然而,为了...
几个字符串处理函数增强版 常用需求基本都能完成 已经编译成DLL 函数列表 兼容字符和串 void revstr char str 字符串反转 int substring char res int pos int len char substr 从pos开始取len个字符到substr中 ...
在C语言中,字符串其实是由字符组成的数组,但由于其特殊性,经常被单独讨论。由于C语言并没有专门的字符串类型,我们通常使用字符数组来表示字符串。然而,指针是C语言中非常重要的一个概念,它能指向内存中的任何...
其中,字符串处理是C语言编程中一个重要的知识点,尤其是字符串的连接操作,更是日常编程中频繁用到的技术之一。根据给定的文件信息,我们将深入探讨C语言中连接字符串的实现方法及相关知识。 ### C语言中的字符串 ...
在进行数据交互时,我们常常需要检查一个字符串是否符合JSON的格式规范,这就是`C语言检测字符串是否为json字符串`这个主题所关注的问题。 C语言本身并不直接支持JSON解析,因此我们需要借助第三方库或者自定义函数...
在C语言中,字符串处理是编程中常见的任务之一。本题目的目标是实现一个字符串快速压缩算法,它将连续重复的字符进行压缩,遵循"字符重复次数+字符"的格式。例如,"xxxyyyyyyz"会被压缩成"3x6yz"。下面我们将详细...
C语言大作业,实现基本字符串编辑,void listinput(char *s);//字符串输入 void listoutput(char *s);//字符串输出 int listlenth(char *s);//求串长 void linklist(char *s,char *t);//串连接 int listcompare(char...
本文将深入探讨C语言中字符串的处理方式,包括字符串字面量的存储机制、字符串变量的定义与初始化、字符数组与指针的区别,以及字符串的常见读写操作等。 #### 字符串字面量的存储机制 在C语言中,字符串字面量是...