字符串指针与字符数组传参

深信服的笔试上被吊打,其中对一道用指针做形参的题目印象十分深刻,借此恶补了一晚上指针,今天总结,以作警示。

​ 试想有如下情形,将一个字符串指针做形参赋值函数修改其字符串,函数结束后字符串被改变了吗?

#include<stdio.h>
void testPstr(char *ppstr){ppstr = "hasten";printf("%s\n",ppstr);
}int main(){char *pstr = "test";printf("%s\n",pstr);testPstr(pstr);printf("%s\n",pstr);  // test ? hasten?
}

结果是没有。

test
hasten
test

我以前天真的以为指针本质上就是地址,把指针传递给函数其实就是把地址传递给函数来操作其实不然,这里编译器的逻辑是这样,pstr本质上是main()函数内的一个局部变量,而函数显然是不能跨函数使用别的函数的变量的(显然这样做会有安全问题),怎么办呢,那就是在函数中拷贝一个同样的变量,接受实参的值来进行操作。

对于指针来说,它的值是地址,传参本质上是函数内定义了一个新指针(这里是ppstr)指向同一个地址,但是和int, double, char这类整形变量的传递不一样的是,char *指向的是一个字符数组,该字符数组存储在程序的.rodata段,也就是存放只读数据的区域,这就是为什么直接修改char *引用的字符串会导致SF:

    char *pstr = "test";pstr[0] = 'Z';  // segment faultprintf("%s\n",pstr);

程序不可能让你修改这段内存的数据,因此ppstr = "hasten" 这段操作本质上是让一个与main毫无关联的指针ppstr从指向rodata内和pstr的一样的内存段, 转为指向rodata的另一个存储着"hasten"字符串的区域, 最后函数结束时ppstr在栈中被销毁,不会影响到main() 中的pstr。

pstr->"test"<-ppstrpstr->"test"
ppstr->"hasten"

验证如下:

#include<stdio.h>
void testPstr(char *ppstr){printf("ppstr_cont:%s ppstr_cont_addr:%#x ppstr_addr:%#x\n",ppstr,ppstr,&ppstr);ppstr = "hasten";printf("ppstr_cont:%s ppstr_cont_addr:%#x ppstr_addr:%#x\n",ppstr,ppstr,&ppstr);}int main(){char *pstr = "test";printf("pstr_cont:%s pstr_cont_addr:%#x pstr_addr:%#x\n",pstr,pstr,&pstr);testPstr(pstr);printf("pstr_cont:%s pstr_cont_addr:%#x pstr_addr:%#x\n",pstr,pstr,&pstr);
}

结果:

pstr_cont:test pstr_cont_addr:0x404039 pstr_addr:0x61fe18
ppstr_cont:test ppstr_cont_addr:0x404039 ppstr_addr:0x61fdf0
ppstr_cont:hasten ppstr_cont_addr:0x404032 ppstr_addr:0x61fdf0
pstr_cont:test pstr_cont_addr:0x404039 pstr_addr:0x61fe18

我这里操作环境是Windows, 之前试过linux,但是每次运行地址的变动相比win要大,不好验证,就改到windows上了,但本质都差不多。

可以看到地址中404开头的是常量存储区,61f开头的是栈区,pstr指向的字符串test的首字符t存储在0x404039,其指针本身地址在0x61fe18, 传入函数以后另拷贝的一个指针ppstr地址在61fdf0,原本也是指向test首字符所在的地址0x404039, 后来指向的hasten首字符t存储在0x404000,自此和pstr不相及。

那要怎么把修改值赋值给pstr呢?方法是用二级指针:

#include<stdio.h>
void testPstr(char **ppstr){printf("ppstr_cont:%s ppstr_cont_addr:%#x ppstr_addr:%#x\n",*ppstr,ppstr,&ppstr);*ppstr = "hasten";printf("ppstr_cont:%s ppstr_cont_addr:%#x ppstr_addr:%#x\n",*ppstr,ppstr,&ppstr);}int main(){char *pstr = "test";printf("pstr_cont:%s pstr_cont_addr:%#x pstr_addr:%#x\n",pstr,pstr,&pstr);testPstr(&pstr);printf("pstr_cont:%s pstr_cont_addr:%#x pstr_addr:%#x\n",pstr,pstr,&pstr);
}

这样传过去的就是pstr的地址了,函数内的ppstr是指向pstr的,右值调用这些指针实际指向的内存如下,具体结果就不放了,大家可以实际验证一下:

入函数前内存:   ppstr->pstr->"test"
入函数后内存:  ppstr->pstr->"ppstr"
引用方式:    &ppstr ppstr  *ppstr&pstr   pstr

另一个方法就是把testPstr改为返回char*指针的函数,就不多说了。

C语言指针难就在于,它的语法规范多(*,&),结合左值引用和右值引用, 整体使用就变得较为繁琐,同样的指针语法,典型的例子就是链表(p->next作为左值表示要指向的地址,p->next作为右值表示p正指向的地址),平时使用时一定要多加谨慎。

再回到上图的例子,如果把char *pstr 改为 char pstr[] , 函数的形参从char * 改为 char [] ,会如何?

void testPArr(char pstrArr[]){pstrArr[0] = 'Z';printf("pstrArr in test:   pstrArr_cont:%s pstrArr_cont_addr:%#X pstrArr_addr:%#X\n\n", pstrArr, pstrArr, &pstrArr);
}int main(){//连续类型char pstr[] = "test";printf("pstr in main:   pstr_cont:%s pstr_cont_addr:%#X, pstr_addr:%#X\n\n",pstr,pstr,&pstr);testPArr(pstr);printf("pstr in main:   pstr_cont:%s pstr_cont_addr:%#X, pstr_addr:%#X\n\n",pstr,pstr,&pstr);}
pstr in main:   pstr_cont:test pstr_cont_addr:0X61FE1B, pstr_addr:0X61FE1BpstrArr in test:   pstrArr_cont:Zest pstrArr_cont_addr:0X61FE1B pstrArr_addr:0X61FDF0pstr in main:   pstr_cont:Zest pstr_cont_addr:0X61FE1B, pstr_addr:0X61FE1B

可以看到在函数中对字符数组的修改就是对pstr原本的地址操作的, 原因是pstr这时候是一个存储了内容为“test"的字符数组,其所有数据都在用户栈区,故可以被修改,当其作为函数参数的时候,编译器会把它解析成一个指向其首元素首地址的指针,void testPArr(char pstrArr[]) 相当于void testPArr(char *pstrArr), 运行结果中也可以看到其有独立的指针地址,是一个指向字符数组的指针。通过指针下标引用所做的修改都会影响到字符数组。

但本质上,函数仍旧是对传入的数据拷贝了仅在函数内作用的指针,故在上述程序若让 pstrArr = "Hasten" 的话还是让指针放弃原来指向的来自用户栈区的字符数组,而指向另一个来自.rodata段的字符串字面量,跟pstr分道扬镳。

如果要修改字符数组内的内容怎么办? 显然无法让pstr = "xxx", 因为pstr是已经分配好数据的数组,是这段数据的标头,不是指针,无法直接引用字符串字面量。唯一的方法是用循环将字符一个个拷贝其分配内存中。

char pstr[] = "test";
char data[] = "data";
pstr = data; //error
for(int i = 0; i < sizeof(data)/sizeof(data[0]); i++){pstr[i] = data[i];
}

显然字符数组和字符指针各有其的特点,比如如果想设置一段字符串的访问属性为只读时就可以用const char *, 而如果想读写就可以用char x[]

总结:

  • 函数无法直接使用别的函数的变量,而是将实参变量拷贝一份,因此可以让函数拷贝指针形参,指向实参的地址就可以对实参变量所在地址的值做修改

  • 字符数组和字符指针的性质不同,字符数组的数据就是数组本身,属于栈段,可以修改,而字符指针是引用来自.rodata段的字符串,修改其引用的字符串会导致程序错误(也就是为什么实践时凡是使用char*都一定定义为const char*

  • C 语言中,当一维数组作为函数参数的时候,编译器总是把它解析成一个指向其首元素首地址的指针。

参考

《C和指针》 指针部分

C语言数组参数与指针参数

C语言补漏:字符串指针与字符数组传参相关推荐

  1. 字符串,字符指针,字符串指针,字符数组

    对于指针可能是大多数程序员最痛苦的事,尤其是指针和字符串和数组三者放在一起的时候,经历了几次错误后,我打算总结一下这几者的区别. 字符串:"asdasfsff",c语言中后面有'\0'. 字符数组:A ...

  2. C语言中将字符串赋值给字符数组的问题

    问不能用赋值语句将一个字符串常量或字符数组直接给一个字符数组赋值." 那么str1="China"是不是错的? char a[ ]='toyou'; 为什么是正确的呢? ...

  3. C语言:字符串赋值给字符数组

    一个字符串可以赋值给一个字符数组,只要不定义这个字符数组的长度就行 例如: char a[]="Hello" 此时存在数组里面,也是一个字符一个位,不包括双引号 如果要输出时 就用 ...

  4. 【C语言】指针第二弹(指针数组、数组指针、数组传参)

    一. 指针数组 指针数组就是存放指针变量的数组,指针数组的本质是数组,而非指针. 1.1 定义和初始化 定义:int* arr[3]  //arr是存放整型指针的数组,包含3个元素 初始化:int* ...

  5. 指针进阶(指针与数组传参、数组指针与指针数组、函数指针数组、回调函数的辨析)

    指针 指针作为C语言中极具代表性的特征之一,也是C语言学习中的一大难点. 简单来说,指针我们需了解的最基础的即: 指针是一个用来存放地址的变量,地址唯一标识一块内存空间. 指针的大小是固定的4/8个字 ...

  6. 字符指针,字符数组,双引号的字符串的区别与联系

    2019独角兽企业重金招聘Python工程师标准>>> 作者 :  陈宗权 先说说双引号的字符串,比如"chenzongquan",它是由里面咱们看到的一系列字符 ...

  7. C++语言篇 第六章 字符数组及函数(不能用在string字符串中)

    字符数组 数组中的每个元素都是一个字符的数组称为"字符数组".有时,把一维字符数组又称为"字符串".定义字符数组的方法与定义其他类型数组的方法类似. 对于字符数 ...

  8. C语言指针操作字符数组demo

    C语言指针操作字符数组demo,本意是使用指针操作source,函数调用一次取16个字节,调用两次获取到整个数据. char source[32] = {0}; int *header = (int ...

  9. C语言:用二维字符数组的每行存储键盘输入的字符串,将这些字符串按字典顺序升序排序,输出排序后的结果。

    /*C语言:用二维字符数组的每行存储键盘输入的字符串,将这些字符串按字典顺序升序排序,输出排序后的结果.*/#include <stdio.h> #include <conio.h& ...

最新文章

  1. 给gridview动态生成radiobutton添加OnCheckedChanged监听函数
  2. 苹果7plus元件分布图_苹果iphone7 plus手机拆解全过程评测 iphone7 plus拆机图解教程...
  3. Linux网络模式及远程连接出错排障
  4. Android 中的Activity的静态变量问题
  5. csu 1578 Opening Ceremony (递推 )
  6. 工具-管理工具资源集合
  7. [mooc]open course on github
  8. VRay Next(4.0) for SketchUp之BIG分析图制作教程
  9. 计算机怎么取消账户密码怎么设置,电脑开机密码怎么设置以及取消
  10. UG NX二次开发(C#)-建模-判断一张面是孔面还是凸台面
  11. 华为核心交换机HW_S7706添加静态路由
  12. 面试必备:高频算法题汇总「图文解析 + 教学视频 + 范例代码」必问之 排序 + 二叉树 部分!
  13. iOS自动化部署方案Jenkins Fastlane code.aliyun 蒲公英 appStore
  14. 代理模式(静态代理和动态代理)
  15. office计算机二级第26套word,全国计算机等级考试二级msoffice高级应用(word部分:第11-20套).docx...
  16. win2003 64 php,win2003 x64 apache php 开发环境配置日志
  17. C与C++中++i和i++的区别
  18. Python爬虫看腻了?JS爬虫来拯救你!
  19. 键盘输入一段英文,输出其中的单词个数。
  20. Mraktext+PicGo

热门文章

  1. 从睡眠期间的大脑活动检测痴呆症
  2. CSS3干货13:把页面变成黑白灰色彩
  3. 聊天室(2)-项目开发文档-李兆龙
  4. 10亿手机号如何去重?(BitMap)
  5. 人脸识别技术开发解决方案,人脸识别智慧校园应用开发
  6. 大学英语四级写作试题必背之35句型
  7. 四级资料免费分享 【写作万能模板 + 听力高频词 + 核心500词 + 翻译必备句型 + 作文对策】 点个关注即可全部拿走!!!
  8. 单片机出现正在检测目标单片机问题解决办法
  9. RuntimeError: applying transform <monai.transforms.croppad.dictionary.RandCropByPosNegLabeld object
  10. <input>标签构建快递信息界面(HTML+CSS)