惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

O
OpenAI News
I
Intezer
C
CERT Recently Published Vulnerability Notes
V
Vulnerabilities – Threatpost
S
Securelist
C
Cyber Attacks, Cyber Crime and Cyber Security
G
GRAHAM CLULEY
P
Palo Alto Networks Blog
P
Privacy & Cybersecurity Law Blog
T
Tenable Blog
T
Threatpost
Latest news
Latest news
Cisco Talos Blog
Cisco Talos Blog
A
Arctic Wolf
罗磊的独立博客
云风的 BLOG
云风的 BLOG
L
LangChain Blog
博客园 - 【当耐特】
P
Privacy International News Feed
The GitHub Blog
The GitHub Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
www.infosecurity-magazine.com
www.infosecurity-magazine.com
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
T
Troy Hunt's Blog
量子位
Security Archives - TechRepublic
Security Archives - TechRepublic
爱范儿
爱范儿
S
Security @ Cisco Blogs
Martin Fowler
Martin Fowler
博客园_首页
SecWiki News
SecWiki News
Spread Privacy
Spread Privacy
I
InfoQ
博客园 - 司徒正美
T
Tor Project blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
H
Help Net Security
L
LINUX DO - 最新话题
H
Heimdal Security Blog
TaoSecurity Blog
TaoSecurity Blog
Hacker News - Newest:
Hacker News - Newest: "LLM"
Vercel News
Vercel News
Y
Y Combinator Blog
D
DataBreaches.Net
T
Threat Research - Cisco Blogs
Stack Overflow Blog
Stack Overflow Blog
C
CXSECURITY Database RSS Feed - CXSecurity.com
M
MIT News - Artificial intelligence
Recorded Future
Recorded Future
博客园 - 叶小钗

博客园 - 泉子

算法题:求一个序列S中所有包含T的子序列(distinct sub sequence) Django模板输出Dict所有Value的效率问题 笔记:LNK2001不代表链接器真的需要链接相关符号 Windows Restart Manager 重启管理器 advapi32.dll kernel32.dll 中的两套注册表API 使用GDI+保存带Alpha通道的图像(续) 使用GDI+保存带Alpha通道的图像 窗口销毁的相关函数与消息,ActiveX控件的销毁 为什么不能将ptr_fun(operator delete)传递给For_Each 两个数之间的最短路径问题 C/C++函数中局部对象的构造与析构时机 四年记——身在中小企业 C++ Const总结 [转载]怎样用VB编写.DLL动态链接库文件 Visual Studio 6.0编译PWLib1.12总结 在Windows上编译PWLib 金山词霸没落了么? SendInput模拟键盘输入的问题 创建用于非Visual C++工具的DLL——VB调用C++ DLL为何必须使用def文件 - 泉子
数组变量在取地址时回归:不再是指针
泉子 · 2010-12-12 · via 博客园 - 泉子

今天去看了看传说中的华为招聘,结果没呆多久,做了几道笔试题和一道上机题,然后就回来了。没什么大意思,不过这道上机题还是个亮点,哈哈。

题目看起来是很简单的,就是给一个字符串,要求将其中的空格字符替换为逗号。

而且程序模板都已经定好,main的内容写好了,转换函数的原型也定义好了。实在是非常简单,简单得让我有点纳闷。很快写好程序:

字符串转换程序

运行一下,问题出来了,程序会在这行代码出现异常:

memcpy(*pOutputString, pInputString, lInputStringLen);

异常内容是Access violation访问空指针,查看一下各变量值。发现问题是*pOutputString的值为0x00000000。

为什么会出现这样的错误?按理说根据传入参数,*pOutputString的值应该是main中定义的数组的地址,数组已经存在于栈中了,那么这个地址就不可能是0x00000000。

问题究竟在哪里,想了好一会,才明白了个中原尤。那就是对于数组的传递和取变量,编译器是做过特殊处理的,数组变量可以如指针一般使用,但并不等同指针。当对它取址时,数组变量将回归其原始涵义。这使得对数组变量取址与直接使用数组变量的效果相同。

我们先从程序的调试输出来找出一些痕迹,在main中设置一个断点。然后运行程序,中断后在Watch查看三个表达式的值:

Watch输出

这里我们能看到,pOutputString与&pOutputString的Value值相同,都是0x0012ff0c。是否是说在地址0x0012ff0c处存了值'0x0012ff0c’,显然不是的,我们看最后一个表达式,这个表达式将地址为0x0012ff0c的内存输出,可以看到,该处连接的四个字节的值均为零。实际上,0x0012ff0c就是数组第一个元素所在的位置,而数组中的元素已被置零。那为什么pOutputString与&pOutputString的值会相同?

为了让这个问题更直白,我们先看看数组与其它数据结构的异同。当我们在程序定义一个int、char、strcut或数组时:

int iTemp = 0;

int iTemps[10] = {0};

其实编译器对这两者所做的工作是类似的,都从栈中保留一块内存区域,用来分别保存一个整数和一组整数。要注意的是iTemp和iTemps只是两个编译器支持的供开发者使用的符号,编译时,编译器将把这两个符号映射为内存的地址。在内存分配上,编译器只是保留保存数据内容大小刚刚好的内存块,并没有为数组分配其它的空间。这也就是说,虽然我们都说数组可以视为等价为指向数组首元素的指针,但这个指针并不是一个真实存在的变量,而是一个类似字面量或立即数的概念。我们可以尝试编译下面的代码:

char pInputString[] = "Hello world ";
char pOutputString[13] = {0};
pOutputString = pInputString;

最后一行代码将得到错误:error C2106: '=' : left operand must be l-value。

我们可以再试试:

1 = 1;

将得到同样的错误,左值必须是一个有内存存储支持的量。

一个真正的指针变量是需要占用目标数据额外的内存存储的,当我们如下定义一个指针变量时:

int *pTemp = new int;

编译将保留两块内存:在堆中保留一块内存用来存放一个整数,在栈中保留一块大小与地址总线位数相同大小的内存用于存储堆中目标数据的地址。

当我们使用pTemp符号时,引用的是栈中的地址。当使用*pTemp符号时,引用的是堆中的整数。

对数组变量来说,在目标数据以外没有受到任何内存的支持。即使编译器被要求将它当作一个指针值,也只能作为右值,而不能作为左值。

因此,我们可以想像对数组变量作取地址操作会出现什么问题,对一个不存在于内存中的值取其存储地址,这是一个矛盾。因此,编译器在这个时候需要将数组变量当作普通数据变量一样的符号来处理,取址时返回其目标数据存储位置的首地址,而这正好与我们直接使用数组变量时的效果一样。这就是刚开始时我们从Watch中观察到的现象的原因。

好了,现在知道问题所在了。我们常说数组会退化为指针,但这只是编译器为了避免内存浪费而设计的特例。它本身依然只是一个变量。以前看《C和指针》等书时,均对数组作了特别的讨论,然而从未想到这点上。很多东西,只有在现实上遇到,才会有深刻的体悟。

然而另外,这道题我还是没正确答出,作为函数的实现者,在不更改函数原型的条件(考题明确要求)下,我想不出一种办法来解决这个问题。当调用者对一个数组变量取址并作强制转换为一个(T **)时,这一切看似合情合理,而我不知道是应该兼容这种状况(将入参char **再强制转换为char*),或是检测这种情况,再或者任其异常。

后话:之前百度了一下,搜了一下对数组变量取地址的相关信息,找到这样的一个帖子,说以下代码:

int a[3][4];
cout<<&a<<endl;
cout<<a<<endl;
cout<<sizeof(&a)<<endl;
cout<<sizeof(a)<<endl;

的输出是:

0012ff40
0012ff40
48
48

对于sizeof(&a)的输出,个人以为不应该是48,无论如何,变量取址的结果都是一个地址值,它的大小应该是4个字节(在32位机器上)。

还好在跟帖中也有人说明在另外一些编译器(GCC 4.4.0,vc2008,icl 11.1)上结果是4。

看来以后得注意一下。