关于C语言的题目
来源:互联网 发布:云计算应用举例 编辑:程序博客网 时间:2024/05/09 19:51
很早以前看到,觉得不错,今天想起来,搜出来备份一下,呵呵
int x=35;
char str[10];
//问:strlen(str)和sizeof(str)的值分别是多少?
// strlen(str) 值不确定,strlen根据'/0'确定字符串是否结束。
// sizeof(str)=10 sizeof一个数组为数组长度
strcpy(str,"www.it315.org"/*共13个字母*/);
//问:此时x和strlen(str)的值分别是多少?
// x 为35
// strcpy(char* dest, const char* src)
// 根据src来复制dest,依照src的'/0'决定复制的长度,而dest必须要提供足够的长度,这里会引起溢出,strlen返回13,但是数组外部的数据已经被破坏
str="it315.org";//编译能通过吗?
// 数组不能赋值,只能初始化。char str[10] = "it315.org";
// 而且初始化时编译器会检查数组的长度与初始化串的长度是否匹配
char *pstr;
strcpy(pstr,"http://www.it315.org");
//上句编译能通过吗?运行时有问题吗?
// 可以通过编译,但是pstr指向了常量区,运行时最好只做读操作,写操作不保险
//(作者注:我下面给出了更确切的答案 )
const char *p1;
char * const p2;
//上面两句有什么区别吗?
// const char* 和 char const* 一样,都是表示指向常量的字符指针。
// char * const 表示指向字符的常量指针
p1=(const char *)str;
//如果是p1=str;编译能够通过吗?明白为什么要类型转换?类型转换的本质是什么?
// 可以通过编译。关于常量与非常量指针的关系是这样的:
// const指针可以指向const或者非const区域,不会造成什么问题。
// 非const指针不能指向const区域,会引起错误。
strcpy(p1,"abc");//编译能够通过吗?
// 不能通过,strcpy( char*, const char*); char* 不能指向const char*
printf("%d",str);//有问题吗?
// 没有问题,输出的是str的地址信息。
pstr=3000;//编译能过吗?如果不行,该如何修改以保证编译通过呢?
// 不能通过,char* pstr表示pstr是个字符指针,不能指向3000的整形变量。
// 修改的话,可以这样:pstr = (char*)3000,把pstr指向3000这个地址;
long y=(long)pstr;//可以这样做吗?
// 可以,y的值为pstr所指的地址。不过如果是纯粹要地址的话,最好是用unsigned long。
int *p=str;
*p=0x00313200;
printf("%s",str);//会是什么效果?提示0x31对应字符'1',0x32对应字符'2'。
// 首先编译未必会过关,有些编译器可能不允许int * 直接指向char*。最好是改为int *p = (int*)str;
// 过关了效果就是什么东西都没有。int *p=str; p为str所指的地址,*p表示修改了str所指向的内存。
// 由于sizeof(int)在32位机上,int有4个字节(其实具体要看编译器的配置文件,好像是limit.h,一般是4个字节)所以修改了str[0]-str[3]
// 由于0x00313200头尾都是0,所以字符串为'/0'开头,什么都打印不出来。这里有个Big-endin和little-endin的问题。以0x31323334为例
// little-endin的机器上面,0x31323334在内存中排列顺序为34 33 32 31,输出为4321,如INTEL芯片的pc
// big-endin机器上面为31 32 33 34 ,输出为1234,如IBM POWERPC
p=3000;//p+1的结果会是多少?
// 3000+sizeof(int); 指针+1均为原来地址加上sizeof(指针所指的数据类型)
char *pc=new char[100];//上述语句在内存中占据几个内存块,怎样的布局情况?
// 本身pc会占用函数栈一个4字节的指针长度(具体是否为4个字节要看机器和编译器)。
// new会在堆上申请100个字节sizeof(char)的连续空间。
void test(char **p)
{
*p=new char[100];
}//这个编译函数有问题吗?外面要调用这个函数,该怎样传递参数?
// 该程序没有问题。需要在函数中对指针所指的地址进行变化是必须传人指针的地址。
// 原因是这样的:如果传入的为指针本身,在函数调用的时候,实参会被复制一个实例,这样就不是原来的指针了,对该指针本身进行的任何改变都不能传递回去了。
// 可以这样理解,如果传入的参数为int,那么对int本身的值的改变就传不回去啦,加个*也是一样的。
//能明白typedef int (*PFUN)(int x,int y)及其作用吗?
// 定义了一个函数指针类型的宏,这样PFUN就表示指向返回值为int,且同时带2个int参数的函数指针类型了。
// 可以用来定义这样的变量:
// 比如有个函数为int fun( int x, int y );
// PFUN p = fun;
第二题:
char str[10];
//问:此时x和strlen(str)的值分别是多少?
strcpy(pstr,"http://www.it315.org");
//上句编译能通过吗?运行时有问题吗?
答: 编译可以通过,但是pstr没有进行有效的初始化,它指向了一个不确定的内存区,运行时会出现内存不可写错误!
OK,言归正转,现在开始对试题中的第二题进行探讨。题目如下:
int x=35;
char str[10];
strcpy(str,"www.it315.org"/*共13个字母*/);
//问:此时x和strlen(str)的值分别是多少?我们先不去探讨答案是多少,但我觉得这题与编译器有关。张老师的答案也不是没有道理,网友秦始皇的回答也有道理。肯定有人开始怀疑了,你到底在说什么?这也对,那也对,究竟什么是对的。好的,我们现在就开始分析。
一、 栈
在具体讲解之前,我们先来明确栈的几个概念:满栈与空栈,升序栈与降序栈。
满栈是指栈指针指向上次写的最后一个数据单元,而空栈的栈指针指向第一个空闲单元。一个降序栈是在内存中反向增长(就是从应用程序空间结束处开始反向增长),而升序栈在内存中正向增长。
RISC机器使用传统的满降序栈(FD Full Descending)。如果使用符合X86规定的编译器,它通常把你的栈指针设置在应用程序空间的结束处并接着使用一个满降序栈。用来存放一个函数的局部变量、参数、返回地址和其它临时变量的栈区域称为栈帧(stack frame)。(关于这部分的详细信息请参看我另外一篇文章《通过Linux内核源码看函数调用之前世今生》)。如图1所示:
图 1栈帧布局
二、 目标文件格式各个系统之间,目标文件格式都不相同。第一个从贝尔实验室诞生的UNIX系统使用的是a.out格式。System V Unix的早期版本使用的是COFF(Common Object File Format 一般目标文件格式)。Windows使用的是COFF的一种变种,叫做PE格式(Portable Executable 可移植可执行)。现代Unix――比如Linux,各种BSD ,以及Sun Solaris――使用的是Unix ELF(Executable and Linkable Format,可执行和可链接格式)。尽管以下的讨论集中在ELF上,但不管是哪种格式,基本的概念都是相似的。
如果变量x和str是局部变量,那么肯定是放在栈中。如果他们两者都是全局变量,那么x放在.data段(.data:存放已初始化的全局变量),str放在.bss段(.bss:存放未初始化的全局变量)。
一个典型的ELF可执行目标文件信息布局如图2所示:
图 2典型的ELF可执行文件格式
每个程序都有一个运行时存储器映像,如图3所示:图 3Linux运行时存储器映像
在Linux系统中,代码段总是从地址0x08048000处开始。数据段在接下来的下一个4KB对齐的地址处。运行时堆在接下来的读写段之后的第一个4KB对齐的地址处,并通过malloc库往上增长。开始于0x40000000处的段是为共享库保留的。用户栈总是从地址0xbfffffff处开始,并向下增长的(向低地址方向增长)。从栈的上部开始于地址0xc0000000处的段是为操作系统驻留存储器的部分的代码和数据保留的。通过这里讲解之后,你应该彻底懂得了满降序栈的含义。三、 寻址与字节顺序
几乎在所有的机器上,多字节对象都以连续的字节序列存放,对象的地址为所使用的字节序列中最小的地址。比如,一个int型的变量x的地址为0x100,也就是说&x=0x100,那么x的四字节将被存储在内存中的0x100,0x101,0x102和0x103。
某些机器选择在存储器中按照从最低有效位字节到最高有效位字节的顺序存储对象,而
另一些机器则按照从最高有效位字节到最低有效位字节的顺序来存储对象。前者我们称为小尾端(little-endian),比如Intel的机器都采用这种规则,后者称为大尾端(big-endian),如IBM,Motorola等机器。
假设x类型为int,地址位于0x100处,有一个16进制的值为0x12345678,分别在大尾端和小尾端的存储方式为:
大尾端:
0x103 0x102 0x101 0x100 ……
78
56
34
12
……小尾端:
0x103 0x102 0x101 0x100 ……
12
34
56
78
……注意,在字0x12345678中,高位字节的16进制为0x12,而低位字节为0x78。不管是在大尾端机器中,还是小尾端机器中,输出的x的值都为0x12345678。
四、 透过汇编代码看变量存储布局
我们从局部变量和全局变量两个方面来,分别在Windows下的VC++6.0和Linux下的GCC来探讨这个题目。
假设程序如下: 1 #include <stdio.h>
2 #include <string.h>
3
4 int main()
5 {
6 int x = 35;
7 char str[10];
8 strcpy(str,"www.it315.org"/*共13个字母*/);
9 printf("%d/n",x);
10 return 0;
11 }
这段程序在VC++6.0中的反汇编代码如下: 1: #include <stdio.h>
2: #include <string.h>
3:
4: int main()
5: {
00401010 push ebp
00401011 mov ebp,esp
00401013 sub esp,50h
00401016 push ebx 栈帧布局 高地址
前一个栈帧
ebp
(x=35)
……
……
(str)……
低地址
00401017 push esi
00401018 push edi
00401019 lea edi,[ebp-50h]
0040101C mov ecx,14h
00401021 mov eax,0CCCCCCCCh
00401026 rep stos dword ptr [edi]
6: int x = 35;
00401028 mov dword ptr [ebp-4],23h /*将35压进栈中*/ (1处)
7: char str[10];
8: strcpy(str,"www.it315.org"/*共13个字母*/);
0040102F push offset string "www.it315.org" (00420020)
00401034 lea eax,[ebp-10h] (2处)
00401037 push eax
00401038 call strcpy (00401100)
0040103D add esp,8
9: printf("%d/n",x);
00401040 mov ecx,dword ptr [ebp-4] (3处)
00401043 push ecx
00401044 push offset string "%d/n" (0042001c)
00401049 call printf (00401080)
0040104E add esp,8
10: return 0;
00401051 xor eax,eax
11: }
从以上代码可以发现,在1处,将x的值35压入ebp-4中,在运行2处之前,已经将字符串的值压入栈中了,然后获取str在栈的地址,即ebp-10h,也就是ebp-16,文中红色箭头所指的对方。装载到eax寄存器中,然后也压入栈中。众所周知,在X86平台上,参数的传递是通过栈帧来实现的,此时调用函数strcpy,将字符串的值拷贝到str的地址处。那么此时如何存放字符串?就是问题的关键所在。大家可能都知道已知str字符串的地址,那么要得到它下一个字符串的值,就是*(str+1),那么答案就出来了。在X86平台上,栈是往下增长的,那么越往高处就是高地址,当进行字符串拷贝时,字符串的地址顺着蓝色的线朝上走。因为该字符串长度为13,所以覆盖了x所在的栈中的值,最后一个字符g也就赋给了x,由于在Intel的机器中,采用的是小尾端存储方式,所以值在栈中的布局如图4所示: 栈帧布局 高地址
前一个栈帧
ebp
(x)g
ro.5
31ti
.www……
低地址
图 4字符串在栈中的布局
当运行到3处时,程序将ebp-4处的值,也就是x的值压入栈中,调用printf,所以打印出来的为103(也就是g的值)。在Linux环境下,GCC编译器似乎表现的技高一筹,得到的答案是35。下面我们来看反汇编后的代码: .file "sttest.c"
.section .rodata
.align 32
.LC0:
.string "www.it315.org"
.string ""
.LC1:
.string "%d/n"
.text
.globl main
.type main, @function
main:
pushl %ebp
movl %esp, %ebp
subl $56, %esp
andl $-16, %esp
movl $0, %eax
subl %eax, %esp
movl $35, -12(%ebp) /*将35放到栈中,即x处*/ (1处) 栈帧布局 高地址
前一个栈帧
ebp
……
……
(x=35)
…………
……
……
……
……
(str)
……
低地址
movl $.LC0, 4(%esp)
leal -40(%ebp), %eax (2处)
movl %eax, (%esp)
call strcpy
movl -12 (%ebp), %eax (3处)
movl %eax, 4(%esp)
movl $.LC1, (%esp)
call printf
movl $0, %eax
leave
ret
.size main, .-main
.section .note.GNU-stack,"",@progbits
.ident "GCC: (GNU) 3.3.5 (Debian 1:3.3.5-13)"
我们从2处可以发现,str的位置在ebp-40处,取得str在栈中的地址,然后放到堆栈指针处,调用strcpy,此时我们不难发现x的地址和str的地址相差40-12=28,远远大于字符串的长度,所以根本不可能覆盖x的值。如果你将字符串的长度改为29个字符,那么就将会覆盖x的值。
下面,我们来讨论x和str为全局变量的情况,也就是将6,7两行代码提到第3行处。
在第二节中,我们讨论过x放在.data段,str放在.bss段,从图三中可以观察出,读写段(.data,.bss)位于低地址处。对于ELF文件,一般会规定代码段的总长度大小,低地址处是.data段,因为.data是已经固定了的,而.bss段是在运行时才会赋值,所以代码段剩下的空间都是.bss的大小,注意此时.bss段的地址大于.data段的地址,所以为.bss中的变量赋值时,根本不可能覆盖.data段的值。把图2倒过来看,大家就会明白了。所以,如果x和str是全局变量,str的值永远不可能覆盖x的值。
五、 总结
其实每一道题目后面都隐藏着很多知识,我们不能只看表面,大概差不多就行了。只要我们深究下去,可以获得比题目本身更多的知识点。
六、 附录
在Windows中,程序运行时存储器映像的资料比较少,我到目前只在《编程卓越之道:深入理解计算机》一书中提及过。所以凭我的印象画下了该图:
图 5程序在Windows中运行时的存储器映像
- 关于C语言的题目
- 请求帮助 几个关于C语言的题目
- 关于C语言字符串操作的几个题目
- 【C语言】一些关于二进制位的一些题题目哦。
- 关于数据存储大小端模式的C语言题目
- 关于c语言的一道题目,适合新手看哦
- 【C语言】再次找的一些C语言题目,关于折半查找和某些种种
- 一道C语言的题目
- 简单的c语言题目
- 有趣的C语言题目
- c语言中关于static的用法,笔试中常见的题目
- C语言中的一个关于求正方形个数的算法题目
- 一道关于二维数组和指针数组的C语言笔试题目
- 好文摘抄 [C语言]关于指针和int型的一道题目
- 这种关于进制的C语言题目,到底如何解答?
- PAT考试乙级1050(C语言实现) (重点题目)(关于保留小数的坑)
- 【C语言】一些有意思的C语言题目,
- 几道C语言的题目
- JavaScript中的正则表达式解析
- vs2005中语法着色的bug
- VB.NET中的TextBox控件详解
- hg
- 第一份收入
- 关于C语言的题目
- CSDN特别收录 --- 超强的指针 *一辈子都找不到的牛B贴
- C#的WebBrowser操作frame如此简单
- 程序员 超越软件蓝领的七种武器
- 2表关联更新
- 用GAPI开发Windows Mobile手机游戏
- Linux下Samba的配置
- Boot Sector
- 智能手机Smartphone开发从零起步(一)