c语言getcharchar数组含义,不要复制

memcpy_百度百科
关闭特色百科用户权威合作手机百科
收藏 查看&memcpy
c和c++使用的内存拷贝函数,memcpy函数的功能是从源src所指的内存地址的起始位置开始拷贝n个字节到目标dest所指的内存地址的起始位置中。外文名memcpy功&&&&能始拷贝n个字节返回值指向dest的指针
void *memcpy(void *dest, const void *src, size_t n);从源src所指的内存地址的起始位置开始拷贝n个字节到目标dest所指的内存地址的起始位置中C语言中使用#include &string.h&;
C++中使用#include &cstring&和#include &string.h&都可以。函数返回指向dest的指针。1.source和destin所指的内存区域可能重叠,但是如果source和destin所指的内存区域重叠,那么这个函数并不能够确保source所在重叠区域在拷贝之前不被覆盖。而使用memmove可以用来处理重叠区域。函数返回指向destin的.
2.如果目标数组destin本身已有数据,执行memcpy()后,将覆盖原有数据(最多覆盖n)。如果要追加数据,则每次执行memcpy后,要将目标数组地址增加到你要追加数据的地址。
注意:source和destin都不一定是数组,任意的可读写的空间均可。void*&__cdecl&memcpy(
void*&dst,
const&void*&src,
size_t&count
&&&&void*ret=
#if&defined(_M_MRX000)||defined(_M_ALPHA)||defined(_M_PPC)
&&&&&extern&void&RtlMoveMemory(void&*,const&void&*,size_t&count);
&&&&&RtlMoveMemory(dst,src,count);
#else&/*defined(_M_MRX000)||defined(_M_ALPHA)||defined(_M_PPC)*/
*copy&from&lower&addresses&to&higher&addresses
while(count--){
&&&&*(char&*)dst&=&*(char&*)
&&&&dst&=&(char&*)dst+1;
&&&&src&=&(char&*)src+1;
#endif&&/*defined(_M_MRX000)||defined(_M_ALPHA)||defined(_M_PPC)*/
return&(ret);
void*&memcpy(void*destaddr,voidconst*srcaddr,size_tlen)
&&&&char*&dest=
&&&&char&const*&src=
&&&&while(len--&0)
&&&&*dest++&=&*src++;
&&&&return&
void*&memcpy(void*dest,constvoid*src,size_tcount)
&&&&assert(dest!=NULL&&&&src!=NULL);
&&&&char*&tmp=
&&&&const&char*&s=
&&&&for(size_t&i=0;i&i++)
&&&&tmp[i]=s[i];
&&&&return&
程序例example1
作用:将s中的字符串复制到字符d中。
//memcpy.c
#include&&stdio.h&
#include&&string.h&
int&main()
&&&&char*&s=&GoldenGlobalView&;
&&&&chard[20];
&&&&clrscr();
&&&&memcpy(d,s,(strlen(s)+1));
&&&&printf(&%s&,d);
&&&&getchar();
&&&&return&0;
}输出结果:Golden Global View
作用:将s中第13个字符开始的4个连续字符复制到d中。(从0开始)
#include&string.h&
&&&&char*&s=&GoldenGlobalView&;
&&&&char&d[20];
&&&&memcpy(d,s+12,4);//从第13个字符(V)开始复制,连续复制4个字符(View)
&&&&d[4]='\0';//memcpy(d,s+12*sizeof(char),4*sizeof(char));也可
&&&&printf(&%s&,d);
&&&getchar();
&&&return&0;
输出结果: View
作用:复制后覆盖原有部分数据
#include&stdio.h&
#include&string.h&
intmain(void)
&&&&char&src[]=&******************************&;
&&&&char&dest[]=&abcdefghijlkmnopqrstuvwxyz0123as6&;
&&&&printf(&destination&before&memcpy:%s\n&,dest);
&&&&memcpy(dest,src,strlen(src));
&&&&printf(&destination&after&memcpy:%s\n&,dest);
&&&&return&0;
输出结果:
destination before memcpy:abcdefghijlkmnopqrstuvwxyz0123as6
destination after memcpy: ******************************as6strcpy和memcpy主要有以下3方面的区别。
1、复制的内容不同。只能复制,而memcpy可以复制任意内容,例如、整型、、等。
2、复制的方法不同。strcpy不需要指定长度,它遇到被复制字符的串结束符&\0&才结束,所以容易溢出。memcpy则是根据其第3个参数决定复制的长度。
3、用途不同。通常在复制字符串时用strcpy,而需要复制其他类型数据时则一般用memcpy
新手上路我有疑问投诉建议参考资料 查看c语言编程题!字符串复制。输入一个字符串,把它复制到一个字符数组中,并输出。_百度知道
c语言编程题!字符串复制。输入一个字符串,把它复制到一个字符数组中,并输出。
提问者采纳
if(dest==src))char *temp=0'while((*dest==*stc++);&#92!=&#39,const char *src){if(dest==NULL||src==NULL)return NULLchar
*strcpy(char *dest
其他类似问题
c语言编程的相关知识
按默认排序
其他1条回答
编写函数.、z变成a)输出。
23,输入一个10进制正整数.&scanf(“%d”. 编写函数. 编写函数、9。编程序找出1000以内的所有完数,能得到该数的补码。
5,并存放于一维数组b中,,并输出其因子(6是一个&quot、y变成z.dat(其中空格字符不复制)、2.dat中。38。
29.磁盘文件a1和a2、负整数和零的个数。
18.编写递归函数. 一个数如恰好等于它的因子之和. 求出1至100之间的素数(只能被1和自身整除的数)并顺序写入文件0,输出到磁盘文件“upper,合并后仍保持字母顺序(如a1中存放,返回在一个整数组中出现次数最多的数及其出现次数;); float search(),输出它的所有质数因子(如180的质数因子为 2、5). 编写一个函数;t”、4;n39,判别该年份是否闰年(定义一个宏以判别该年份是否闰年).main();p=search(score。已有有调用语句p(n,在n个元素的一维数组中、有4名学生每个学生考4门课程。若原链表为。
19、多少个负数:将每一行的元素同除以该行上绝对值最大的元素.dat复制到d2:&quot、设计一递归函数实现将小于32768的整数按逆序输出,求m行、多少个负数,m),ack(m,要求以指针变量而不是数组名作参数。
20,将大于或等于60分的学生成绩再形成一个新的文件SCORE60,要求在用户输入学生学号以后能输出该生的全部成绩、…。所谓回文数就是将一个数从左向右与从右向左读是一样的. 输入两个数组(数组元素个数自定)、以下函数p的功能是用递归方法计算x的n阶勒让德多项式的值,要求仅复制d1;printf(“the score of NO。
21。49,将输入的字符串按与输入相反的顺序输出,3)、多少个零、请编写程序,并返回统计结果. 设计一个函数,每个记录包括num,a2中存放&quot,要求仅复制d1. 输入20个整数存入一数组。
13;for(I=0.dat中除英文字符和数字以外的其它内容,,统计m行n列二维数组中有多少个正数、n列的二维数组全体元素中负数的个数。
8;0)46,},用print函数输出这些记录,则输出3、设计一递归函数实现将任一正整数转达换成二进制数;,各自存放一个已按字母顺序排好的字符串,score[3]:PX(X. 写一函数。41、有4名学生每个学生考4门课程. 输入一个正整数。
26,站长团上有产品团购,将字母变成其下一字母(a变成b.dat复制到文本文件d2。
2,每个记录包含学号(8位字符)和成绩(三位整数)两个数据项。
9.编程,把文本文件d1:”)。
35。47,在n个元素的一维数组中;accel&quot,求任意阶多项式 a0+a1X+a2X2+.dat复制到d2:从键盘上读入一个16进制无符号数m、编制一程序。编制程序,要求在主函数中输入字符串及m的值并输出复制结果。另外、编制一程序实现将十进制数转换成二进制数、3。
10.给定年份year,在给定的一行以&quot。44.+anXn
的值并返回多项式的值、编写一函数实现将一个整数按逆序存放到一个数组中。
11.输入一行小写字母后,调用函数rightrot将m中的原始数据循环右移n位,在被调用函数中完成复制、5),2,编程合并二个文件到a3文件中、x变成y、最小值并传送到调用函数、请编程、-1).dat(其中大写英文字母要转换为小写字母),n-1))
当n&gt,该数组中有5个学生的数据记录. 输入两个数组(数组元素个数自定).dat中存放了一组整数.DAT 存放着程序设计基础的考试成绩;请编写p函数,用指针来实现。
31.已知head指向一个带头结点的单向链表。
22.DAT保存在A盘上、编制函数实现在一个带头结点(head)的单向链表(数据有序)中删除相同数据的结点(相同数据的结点只保留一个),输出其中能被数组中其它元素整除的那些数组元素、设文件number,例如31都是回文数;0时50、编制一程序,n)=
ack(m-1;I&lt、平均成绩和及格人数;结束的字符中.;ilrz&quot,将字符串的第m个字符开始的全部字符复制成另一个字符串,找出最大值。
30.已知顺序文件C,然后从第一个字母开始间隔的输出该串,把文本文件d1. 编程;acceillrz&quot. 把文本文件d1,,输出在两个数组中都出现的元素(如a[5]=. 把文本文件d1。
15:从键盘输入一个字符串;printf(“enter the number of student。
34,计算并输出方程X2+Y2=1989的所有整数解:&#92。37。48. 编程、设计一递归函数计算x的n次方,m);,将其中的小写字母全部转换成大写字母,要求在用户找出有不及格课程的学生学号并输出全部成绩:ack(m、设计一递归函数计算下列函数、编一程序实现求所有不超过200的N值,用指针型函数来实现,输出在两个数组中都不出现的元素(如a[5]=,m&}float search(float (pointer)[4]. 编程,打入月份号,要求用指针数组处理.txt”中去保存。并输出移位前后的内容。递归公式如下,将字符数组S2中的全部字符拷贝到字符数组S1中(不用strcpy函数)、c变成d。
28,统计比相邻元素大的数组元素个数并将统计数返回(不考虑a[0]和a[n-1]),int n){}36。请用指针完成,x),则输出2。请编程统计并输出文件中正整数,链表中每个结点包含数据域和指针域。
17.编一个程序、设计一递归函数实现将小于32768的整数按逆序输出、7,1)
ack(m-1.dat复制到文本文件d2,将字符computer赋给一个字符数组、3;4、设计一递归函数实现如下函数。43;完数&quot,用主函数输入这些记录、b变成c,b[6]=、6。
3,N的平方是具有对称性质的回文数、n列的二维数组.dat。
16,然后输出它所对应的八进制。
27。1 2 3 4 5 16 17 18 19 6 15 22 21 20 7 14 23 24 25 8 13 12 11 10 940,找出最长的单词并输出;int I!”结束。请编写函数float *search(),使给出一个数的原码;:head c b a
∧32.%dare。请编写程序实现如图所示链表的逆置,打印一个学生的成绩数组,&m)。
14,输出该月的英文月名、3。输入的字符串以“,N)=X-X2+X3-X4+……+(-1)N-1XN(N&gt. 编写函数.dat中、编程实现如下5*5阶的螺旋方阵。42。
12、十六进制数。
24,则a3中为&quot。45,m,*(p+I)).dat中的英文字符、10,b[6]=,多少个零:head a b C
∧逆置后链表应为;I++)printf(“%52f&#92,从文件中读入学生成绩;n”. 编写一个函数print:1XPX(X)=
((2N-1)*X* Pn-1(X)-(n-1)* Pn-2(X)&#47,然后输出. 编程. 编写函数,p,处理n行。
7,并显示出学生总人数(少于1000),它的因子是1. 编写一个函数。
4数,这个数就称为“完数”。
等待您来回答
下载知道APP
随时随地咨询
出门在外也不愁1273人阅读
我们可以让指针指向某类变量,并替代该变量在程序中使用;我们也可以让指针指向一维、二维数组或字符数组,来替代这些数组在程序中使用,给我们在编程时带来许多方便。
下面我们定义一种特殊的数组,这类数组存放的全部是指针,分别用于指向某类的变量,以替代这些变量在程序中的使用,增加灵活性。指针数组定义形式:
类型标识*数组名[数组长度]
例如: char *str[4];
由于[ ] 比*优先权高,所以首先是数组形式str[4 ],然后才是与“*”的结合。这样一来指针数组包含4个指针s t r [ 0 ]、s t r [ 1 ]、s t r [ 2 ]、s t r [ 3 ],各自指向字符类型的变量。例如: int *p t r [ 5 ] ;
该指针数组包含5个指针p t r [ 0 ]、p t r [ 1 ]、p t r [ 2 ]、p t r [ 3 ]、p t r [ 4 ],各自指向整型类型的变量。
[例6-22] 针对指针数组的应用,我们分别用指针数组的各指针指向字符串数组、指向一维整型数组、指向二维整型数组。
#include &stdlib.h&
#include &stdio.h&
m a i n ( )
char *ptr1[4]={&china&,&chengdu&,&sichuang&,&chongqin&};
/* 指针数组p t r 1 的4个指针分别依此指向4个字符串* /
int i,*ptr2[3],a[3]={1,2,3},b[3][2]={1,2,3,4,5,6};
for(i=0;i&4;i++)
printf(&\n%s&,ptr1[i]);/依*此输出ptr1数组4个指针指向的4个字符串*/
printf(&\n&);
for(i=0;i&3;i++)
ptr2[i]=&a[i];/*将整型一维数组a的3个元素的地址传递给指针数组ptr2*/
for(i=0;i&3;i++)/*依此输出ptr2所指向的3个整型变量的值*/
printf(&%4d&,*ptr2[i]);
printf(&\n&);
for(i=0;i&3;i++)
ptr2[i]=b[i];/*传递二维数组b的每行首地址给指针数组的4个指针*/
for(i=0;i&3;i++)/*按行输出*/
printf(&%4d%4d\n&,*ptr2[i],*ptr2[i]+1);
程序中指针数组与所指对象的关系如图6-12所示。
ptr1指针数组中的4个指针分别指向4个字符串,如图6-11的a)所示,程序中依此输出;ptr2指针数组共有3个指针,若将整型一维数组a中各元素地址分别传递给指针数组的各指针,则ptr2[0]就指向a[0];ptr2[1]就指向a[1];ptr2[2]就指向a[2]。若将二维数组各行的首地址分别传递给指针数组的各指针,如图6-11b)所示,这样一来,ptr2[0]就指向了b数组的第0行,该行有两个元素,其地址为ptr2[0]与ptr2[0]+1;相应指针数组第i个元素ptr2[i]指向的b数组的第i行两个元素地址分别为ptr2[i]与ptr[i]+1。
在处理二维字符数组时,我们可以把二维字符数组看成是由多个一维字符数组构成,也就是说看成是多个字符串构成的二维字符数组,或称为字符串数组。
指针数组对于解决这类问题(当然也可以解决问题)提供了更加灵活方便的操作。
有一点需要说明,若定义一个指针数组后,指针数组各元素的取值(即地址)要注意安全性。
如定义指针数组:
char*ptr[3];
我们说该数组包含三个指针,但指针的指向是不确定的,指针现在可能指向内存的任一地址。假定现在作语句:scanf(&%s&,ptr[i]),则输入的字符串在内存的存放其地址由ptr[i]决定。除非给指针数组元素赋值安全的地址。
[例6-23]定义字符指针数组,包含5个数组元素。同时再定义一个二维字符数组其数组大小为5*10,即5行10列,可存放5个字符串。若将各字符串的首地址传递给指针数组各元素,那么指针数组就成为名副其实的字符串数组。下面对各字符串进行按字典排序。
在字符串的处理函数中,strcmp(str1,str2)函数就可以对两个字符串进行比较,函数的返回值&0、=0、&0分别表示串str1大于str2、str1等于str2、str1小于str2。再利用strcpy()函数实现两个串的复制。下面选用冒泡排序法。
#include&stdlib.h&
#include&string.h&
#include&stdio.h&
char*ptr1[4],str[4][20],temp[20];
/*定义指针数组、二维字符数组、用于交换的一维字符数组*/
for(i=0;i&4;i++)
gets(str[i]);/*输入4个字符串*/
printf(&\n&);
for(i=0;i&4;i++)
ptr1[i]=str[i];/*将二维字符数组各行的首地址传递给指针数组的各指针*/
printf(&original string:\n&);
for(i=0;i&4;i++)/*按行输出原始各字符串*/
printf(&%s\n&,ptr1[i]);
printf(&ordinal string:\n&);
for(i=0;i&3;i++)/*冒泡排序*/
for(j=0;j&4-i-1;j++)
if(strcmp(ptr1[j],ptr1[j+1])&0)
{strcpy(temp,ptr1[j]);
strcpy(ptr1[j],ptr1[j+1]);
strcpy(ptr1[j+1],temp);
for(i=0;i&4;i++)/*输出排序后的字符串*/
printf(&%s\n&,ptr1[i]);
程序中一定要注意指针的正确使用。一旦将二维字符数组的各行首地址传递给指针数组的各指针,则相当于给指针分配了安全可操作的地址,地址空间大小由二维字符数组来决定。
当然也可由编译系统为指针分配地址用于字符串的存放。
[例6-24]利用malloc()函数为指针分配存储空间,实现字符串的排序。
#include&stdlib.h&
#include&string.h&
#include&stdio.h&
char *ptr1[4],*
for(i=0;i&4;i++)
ptr1[i]=malloc(20);/*为指针数组各指针分配20字节的存储空间*/
gets(ptr1[i]);
printf(&\n&);
printf(&original string:\n&);
for(i=0;i&4;i++)
printf(&%s\n&,ptr1[i]);
printf(&ordinal string:\n&);
for(i=0;i&3;i++)
for(j=0;j&4-i-1;j++)
if(strcmp(ptr1[j],ptr1[j+1])&0)
temp=ptr1[j];/*利用指向字符串的指针,进行指针地址的交换*/
ptr1[j]=ptr1[j+1];
ptr1[j+1]=
for(i=0;i&4;i++)/*字符串输出*/
printf(&%s\n&,ptr1[i]);
运行程序,其结果与上述例6-23完全相同
[例6-25]对已排好序的字符指针数组进行指定字符串的查找。字符串按字典顺序排列,查找算法采用二分法,或称为折半查找。
折半查找算法描述:
1.设按开序(或降序)输入n个字符串到一个指针数组。
2.设low指向指针数组的低端,high指向指针数组的高端,mid=(low+high)/2
3.测试mid所指的字符串,是否为要找的字符串。
4.若按字典顺序,mid所指的字符串大于要查找的串,表示被查字符串在low和mid之间,否则,表示被查字符串在mid和high之间。
5.修改low式high的值,重新计算mid,继续寻找。
#include&stdlib.h&
#include&alloc.h&
#include&string.h&
#include&stdio.h&
char*binary();/*函数声明*/
char*ptr1[5],*
for(i=0;i&5;i++)
ptr1[i]=malloc(20);/*按字典顺序输入字符串*/
gets(ptr1[i]);
printf(&\n&);
printf(&original string:\n&);
for(i=0;i&5;i++)
printf(&%s\n&,ptr1[i]);
printf(&input search string:\n&);
temp=malloc(20);
gets(temp);/输*入被查找字符串*/
temp=binary(ptr1,temp,i);/*调用查找函数*/
if(temp)printf(&succesful-----%s\n&,temp);
elseprintf(&nosuccesful!\n&);
char *binary(char *ptr[],char *str,int n)定义返回字符指针的函数*/
{/*折半查找*/
int hig,low,
while(low&=hig)
mid=(low+hig)/2;
if(strcmp(str,ptr[mid])&0)
hig=mid-1;
elseif(strcmp(str,ptr[mid])&0)
low=mid+1;
else return(str);/*查帐成功,返回被查字符串*/
return NULL; / *查找失败,返回空指针* /
[例6-26] 在一个已排好序的字符串数组中,插入一个键盘输入的字符串,使其继续保持有序。
在上述程序查找成功的基础上,我们将该字符串插入到字符数组中。插入的位置可以是数组头、中间或数组尾。查找的算法采用折半算法,找到插入位置后,将字符串插入。
#include &stdlib.h&
#include &alloc.h&
#include &string.h&
#include &stdio.h&
m a i n ( )
int binary(); / *查找函数声明* /
void insert(); / *插入函数声明* /
char *temp,*ptr1[6];
for (i=0;i&5;i++)
ptr1[i]=malloc(20);/*为指针分配地址后*/
gets(ptr1[i]);/*输入字符串*/
ptr1[5]=malloc(20);
printf(&\n&);
printf(&original string:\n&);
for(i=0;i&5;i++)/*输出指针数组各字符串*/
printf(&%s\n&,ptr1[i]);
printf(&input search string:\n&);
temp=malloc(20);
gets(temp);/*输入被插字符串*/
i=binary(ptr1,temp,5)/*;寻找插入位置i*/
printf(&i=%d\n&,i);
insert(ptr1,temp,5,i);/*在插入位置i处插入字符串*/
printf(&outputstrings:\n&);
for(i=0;i&6;i++)/*输出指针数组的全部字符串*/
printf(&%s\n&,ptr1[i]);
int binary(char*ptr[],char*str,intn)
{/*折半查找插入位置*/
int hig,low,
if(strcmp(str,ptr[0])&0)return0;
/*若插入字符串比字符串数组的第0个小,则插入位置为0*/
if(strcmp(str,ptr[hig])&0)
/*若插入字符串比字符串数组的最后一个大,则应插入字符串数组的尾部*/
while(low&=hig)
mid=(low+hig)/2;
if(strcmp(str,ptr[mid])&0)
hig=mid-1;
else if(strcmp(str,ptr[mid])&0)
low=mid+1;
else return(mid);/*插入字符串与字符串数组的某个字符串相同*/
/*插入的位置在字符串数组中间*/
void insert(char*ptr[],char*str,intn,inti)
for(j=n;j&i;j--)/*将插入位置之后的字符串后移*/
strcpy(ptr[j],ptr[j-1]);
strcpy(ptr[i],str);将被插字符串按字典顺序插入字符串数组*/
在程序中,字符串数组的6个指针均分配存放20字节的有效地址。语句ptr1[5]=malloc(20)保证插入字符串后,也具有安全的存储空间,字符串的长度以串中最长的为基准向系统申请存储空间,以保证在串的移动中有足够的存储空间。
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:58775次
排名:千里之外
转载:40篇
(8)(1)(4)(2)(3)(3)(1)(2)(14)(5)(1)(4)c语言中定义一个字符数组,其中存放的是什么_百度知道
c语言中定义一个字符数组,其中存放的是什么
还是a[59]中存放的是'0'\&#92c语言中定义一个字符数组;0'0&#39,还没初始化,比如定义个char a[60],那么是a[0]中存放的&#39,还是全部存放的都是'\;
后会自动加&#39,整型数组也是以'0'&;为结束符吗;0&#39,那在最后一个字符'0'&#92?也会在定义的最后一个元素后自动加&#39,如果是int a[60]=;0'\\吗;吗?
再补充一下,我比较菜?如何求出整型数组的长度?不好意思如果初始化char a[60]=&quot
提问者采纳
你定义的char a[60],因为数组是从a[0]开始计算的 如果你没有赋值 那么默认的是全是0 也就是全是空格 以内你定义的是字符型。 ‘\0’是系统给你加的 它的位置是a[60]的60个元素的后面 不占用你定义的60个元素 你定义的是字符数组 不是int 行数组 char定义的是放字符 字符串的
提问者评价
感谢大家的答问,学到了不少东西
其他类似问题
按默认排序
其他6条回答
0&quot,而数组却不同;\0&quot,否则就会出错,你的字母个数必需小于60.如果小于60:你用字符串赋值的话后面系统就会自动加&quot,你可以正好等于60;,则后面当然是0了简单一点说,系统会自动加&0&quot这个问题是你把数组与字符串搞混了;,也就是说最后一个数字不一定等于&quot,数组赋值后面是不自加& 比方说char[60]=&\\0&quot,如果小于60的话;\;i a m&quot,你用字符串赋值的话;的;
自己试一下就知道了,先定义一个字符数组(不初始化),用gets函数输出,如果什么也没有就说明未初始化时全部赋'\0',如果是乱码就说明是随机值。部分初始化我知道,后面未被赋值的部分自动赋'\0'
总体来说,数组未初始化之前都是不可知的,如果强行输出时会是乱码。对于字符型数组,只要定义个数小于等于定义的个数,它都会自动加/0。对于整型数组,不会加/0。
在c/c++中,数组是指 连续分布的内存单元。字符串是一种字符数组,比其他数组特殊的地方是如果数组被赋值(scanf,fread,fprintf,strcpy,nstrcpy)会在数组末尾追加'\0'.在c语言中,数组大小是不可知的。(strlen(s)只能算出从s开始到'\0'的长度)
全部都是随机值
c语言的相关知识
您可能关注的推广
等待您来回答
下载知道APP
随时随地咨询
出门在外也不愁1您所在的位置: &
C语言结构体里的成员数组和指针(1)
C语言结构体里的成员数组和指针(1)
单看这文章的标题,你可能会觉得好像没什么意思。你先别下这个结论,相信这篇文章会对你理解C语言有帮助。这篇文章产生的背景是在微博上,看到@Laruence同学出了一个关于C语言的题,微博链接。微博截图如下。我觉得好多人对这段代码的理解还不够深入,所以写下了这篇文章。
单看这文章的标题,你可能会觉得好像没什么意思。你先别下这个结论,相信这篇文章会对你理解C语言有帮助。这篇文章产生的背景是在微博上,看到同学出了一个关于C语言的题,。微博截图如下。我觉得好多人对这段代码的理解还不够深入,所以写下了这篇文章。
为了方便你把代码copy过去编译和调试,我把代码列在下面:
#include&&stdio.h&& &struct&str{& &&&&&int&& &&&&&char&s[0];& &};& &&& &struct&foo&{& &&&&&struct&str&*a;& &};& &&& &int&main(int&argc,&char**&argv)&{& &&&&&struct&foo&f={0};& &&&&&if&(f.a-&s)&{& &&&&&&&&&printf(&f.a-&s);& &&&&&}& &&&&&return&0;& &}&
你编译一下上面的代码,在VC++和GCC下都会在14行的printf处crash掉你的程序。&说这个是个经典的坑,我觉得这怎么会是经典的坑呢?上面这代码,你一定会问,为什么if语句判断的不是f.a?而是f.a里面的数组?写这样代码的人脑子里在想什么?还是用这样的代码来玩票?不管怎么样,我个人觉得这主要还是对C语言理解不深,如果这算坑的话,那么全都是坑。
接下来,你调试一下,或是你把14行的printf语句改成:
printf(&%x\n&,&f.a-&s);&
你会看到程序不crash了。程序输出:4。 这下你知道了,访问0&4的内存地址,不crash才怪。于是,你一定会有如下的问题:
1)为什么不是 13行if语句出错?f.a被初始化为空了嘛,用空指针访问成员变量为什么不crash?
2)为什么会访问到了0&4的地址?靠,4是怎么出来的?
3)代码中的第4行,char s[0] 是个什么东西?零长度的数组?为什么要这样玩?
让我们从基础开始一点一点地来解释C语言中这些诡异的问题。
结构体中的成员
首先,我们需要知道&&所谓变量,其实是内存地址的一个抽像名字罢了。在静态编译的程序中,所有的变量名都会在编译时被转成内存地址。机器是不知道我们取的名字的,只知道地址。
所以有了&&栈内存区,堆内存区,静态内存区,常量内存区,我们代码中的所有变量都会被编译器预先放到这些内存区中。
有了上面这个基础,我们来看一下结构体中的成员的地址是什么?我们先简单化一下代码:
struct&test{& &&&&&int&i;& &&&&&char&*p;& &};&
上面代码中,test结构中i和p指针,在C的编译器中保存的是相对地址&&也就是说,他们的地址是相对于struct test的实例的。如果我们有这样的代码:
struct&test&t;&
我们用gdb跟进去,对于实例t,我们可以看到:
#&t实例中的p就是一个野指针& &(gdb)&p&t& &$1&=&{i&=&0,&c&=&0&'\000',&d&=&0&'\000',&p&=&0x4003e0&&1\355I\211\...&}& &&& &#&输出t的地址& &(gdb)&p&&t& &$2&=&(struct&test&*)&0x7fffffffe5f0& &&& &#输出(t.i)的地址& &(gdb)&p&&(t.i)& &$3&=&(char&**)&0x7fffffffe5f0& &&& &#输出(t.p)的地址& &(gdb)&p&&(t.p)& &$4&=&(char&**)&0x7fffffffe5f4&
我们可以看到,t.i的地址和t的地址是一样的,t.p的址址相对于t的地址多了个4。说白了,t.i 其实就是(&t + 0&0), t.p 的其实就是 (&t + 0&4)。0&0和0&4这个偏移地址就是成员i和p在编译时就被编译器给hard code了的地址。于是,你就知道,不管结构体的实例是什么&&访问其成员其实就是加成员的偏移量。
下面我们来做个实验:
struct&test{& &&&&&int&i;& &&&&&short&c;& &&&&&char&*p;& &};& &&& &int&main(){& &&&&&struct&test&*pt=NULL;& &&&&&return&0;& &}&
编译后,我们用gdb调试一下,当初始化pt后,我们看看如下的调试:(我们可以看到就算是pt为NULL,访问其中的成员时,其实就是在访问相对于pt的内址)
(gdb)&p&pt& &$1&=&(struct&test&*)&0x0& &(gdb)&p&pt-&i& &Cannot&access&memory&at&address&0x0& &(gdb)&p&pt-&c& &Cannot&access&memory&at&address&0x4& &(gdb)&p&pt-&p& &Cannot&access&memory&at&address&0x8&
注意:上面的pt-&p的偏移之所以是0&8而不是0&6,是因为内存对齐了(我在64位系统上)。关于内存对齐,可参看《》一文。
好了,现在你知道为什么原题中会访问到了0&4的地址了吧,因为是相对地址。
相对地址有很好多处,其可以玩出一些有意思的编程技巧,比如把C搞出面向对象式的感觉来,你可以参看我正好11年前的文章《》(用指针类型强转的危险玩法&&相对于C++来说,C++编译器帮你管了继承和虚函数表,语义也清楚了很多)
指针和数组的差别
有了上面的基础后,你把源代码中的struct str结构体中的char s[0];改成char *s;试试看,你会发现,在13行if条件的时候,程序因为Cannot access memory就直接挂掉了。为什么声明成char s[0],程序会在14行挂掉,而声明成char *s,程序会在13行挂掉呢?那么char *s 和 char s[0]有什么差别呢?
在说明这个事之前,有必要看一下汇编代码,用GDB查看后发现:
对于char s[0]来说,汇编代码用了lea指令,lea & 0&04(%rax), & %rdx
对于char*s来说,汇编代码用了mov指令,mov 0&04(%rax), & %rdx
lea全称load effective address,是把地址放进去,而mov则是把地址里的内容放进去。所以,就crash了。
从这里,我们可以看到,访问成员数组名其实得到的是数组的相对地址,而访问成员指针其实是相对地址里的内容(这和访问其它非指针或数组的变量是一样的)
换句话说,对于数组 char s[10]来说,数组名 s 和 &s 都是一样的(不信你可以自己写个程序试试)。在我们这个例子中,也就是说,都表示了偏移后的地址。这样,如果我们访问 指针的地址(或是成员变量的地址),那么也就不会让程序挂掉了。
正如下面的代码,可以运行一点也不会crash掉(你汇编一下你会看到用的都是lea指令):
struct&test{& &&&&&int&i;& &&&&&short&c;& &&&&&char&*p;& &&&&&char&s[10];& &};& &&& &int&main(){& &&&&&struct&test&*pt=NULL;& &&&&&printf(&&s&=&%x\n&,&pt-&s);&&&&&&printf(&&i&=&%x\n&,&&pt-&i);&&&&&&printf(&&c&=&%x\n&,&&pt-&c);& &&&&&printf(&&p&=&%x\n&,&&pt-&p);& &&&&&return&0;& &}&
看到这里,你觉得这能算坑吗?不要出什么事都去怪语言,想想是不是问题出在自己身上。
内容导航&第 1 页: &第 2 页:
关于&&的更多文章
很久很久以前,冬天爱上了夏天,可是他们始终不能相见,后来,他
网友评论TOP5
本次的专刊为大家提供了Oracle最新推出的Java SE 8详细的开发教程,从解读到探究Java 8最新
讲师: 0人学习过讲师: 35人学习过讲师: 58人学习过
借助Google的三大论文,Hadoop打开了低成本海量数据处
春运大军前天正式启动了。昨天的新闻有几条不怎么好的
每年的一月份大约都是在看似忙忙碌碌中度过的。最近一
本书分为8章,首先介绍ASP.NET的开发技巧和重点技术,尤其针对初学者如何快速入门并掌握ASP.NET编程做了深入浅出的介绍;然后重
51CTO旗下网站

我要回帖

更多关于 c语言getchar 的文章

 

随机推荐