小知识点

一个由C/C++编译的程序占用的内存分为以下几个部分

1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,动态局部变量的值等。其操作方式类似于数据结构中的栈。 2、堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。

3数据域(或静态区)(static)—,全局变量和静态变量的存储是放在一块的;初始化的全局变量和静态变量在一块区域.data段, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域.bss段。 - 程序结束后有系统释放

4、文字常量区 —常量字符串就是放在这里的。 程序结束后由系统释放

5、程序代码区—存放函数体的二进制代码, 可执行指令放在代码段中,任何时刻,内存中只有一份相同程序的指令拷贝,多个实例共享这些代码。。

例子

int a = 0; //数据域   
void main(int argc, char **argv)

{

  int b; //栈

  char s[] = “abc”; //s在栈, abc在文字常量区

   char *p1,*p2; //栈

  char *p3 = “123456”; //123456在常量区,p3在栈上

  static int c =0; //数据域

  p1 = (char *)malloc(10); //p1在栈,分配的10字节在堆

   p2 = (char *)malloc(20); //p2在栈,分配的20字节在堆

   strcpy(p1, “123456789”); //123456789放在常量区
  printf(“%s ”,p1);
   free (p1);
   free (p2);

    

}

1. 栈的大小是固定的。如果申请的空间超过栈的剩余空间时,将提示段错误。堆的大小受限于计算机系统中有效的虚拟内存。(递归编程测试进程栈的大小)


2 栈分配的空间是连续的,而堆(用户)分配的空间是不连续的(编程实践)


3 系统响应:栈:只要栈的空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。堆:首先应该知道操作系统有一个记录空闲内存地址的链表;当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的free语句才能正确的释放本内存空间。另外,找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。
 说明:对于堆来讲,频繁的malloc/free势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。对于栈来讲,则不会存在这个问题

4 申请效率:(1)栈由系统自动分配,速度快。但程序员是无法控制的(2)堆是调用malloc函数族分配的内存,一般速度比较慢,而且容易产生碎片,不过用起来最方便。
5 堆和栈中的存储内容:栈:在函数调用时,第一个进栈的主函数中后的下一条语句的地址,然后是函数的各个参数,参数是从右往左入栈的,然后是函数中的局部变量。注:静态变量是不入栈的。当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续执行。 堆:一般是在堆的头部用一个字节存放堆的大小。
6 分配方式:堆都是动态分配的,没有静态分配的堆。栈有两种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由alloca函数进行分配,但是栈的动态分配和堆是不同的。它的动态分配是由编译器进行释放,无需手工实现

静态分配发生在程序编译和连接的时候。动态分配则发生在程序调入和执行的时候

例子

int *p = (int*)malloc(20);//动态在堆区分配空间,必须人为释放

 free(p);

 int *p2 = (int*)alloca(20);//动态在栈区分配空间,自动释放。

 int *p3 = (int *)calloc(4,4);//动态在堆区分配空间,必须人为释放

 free(p3);  

int *p4 = (int*)realloc(p,30);////动态在堆区扩充内存空间,必须人为释放

 free(p4); !

例子:传二维数组

#include<stdio.h>

int Sum(int (*a)[3],int n)

{

 int i,j;

 int sum = 0;

 for(i = 0;i < n; i++)

 {  

   sum += a[i][i];   

}  

for(i = 0,j = n - 1; i < n,j >=0;i++,j--)

 {   

  sum += a[i][j]; 

     }

   printf("sum = %d ",sum);

}

int main()

{

   int b[3][3] = {{5,6,7},{1,2,3},{1,2,3}};

    Sum(b,3); //数组名作为指针永远表示首元素的地址

}

 数组初始化与赋值的问题!!!!!

一、数组大小容易被忽视,如,定义 char str[10] ;程序代码中往往对str[10]进行了赋值,所造成的结果必然是访问了非法内存,引起“断错误”

或者是提示内存错误。不过我曾经有一段代码,对str[10]进行了赋值,但是在虚拟机里运行没有出错,但是经过 arm-linux-gcc 编译后下

载到ARM板上运行时,出现 “”segmentation fault“ ,当时查错3个小时,实在悲剧,估计两种环境下对内存错误的容忍度不同吧!

二、将字符串赋值给一个数组。如果在刚定义时以这种方式赋值: char str[]="12345";是可以的。

但是,如果是: char str[5];str="1234";编译时则会出现警告:str.c: In function ‘main’: str.c:5:3: error: incompatible types when assigning to type ‘char[10]’ from type ‘char *’ str.c:8:2: warning: format ‘%s’ expects type ‘char *’, but argument 2 has type ‘int’

运行则出现:root@linux-VirtualBox:~/test/example#./str 1234 段错误

但是:如果数组是以形参的形式定义的,则可以在子函数里 用 str=”1234“;进行赋值。????????

三、比较好的赋值方式。比如: 用strcpy(str1,str2)等函数将字符串赋值给数组。

四、整型数组不能用printf("%s ",str);来输出,字符数组才可以。

fgets() 函数使用时需要注意的问题!!!!!!!!!!

一、原型是char *fgets(char *s, int n, FILE *stream);  作用是从流中读取n-1个字符,除非读完一行,参数s是来接收字符串,如果成功则返回s的指针,否则返回NULL。  

 

  形参注释:*s结果数据的首地址;n-1:一次读入数据块的长度,其默认值为1k,即1024;stream是文件指针

 

  例:如果一个文件的当前位置的文本如下

 

  Love ,I Have

 

  但是,如果用 

 

  fgets(str1,4,file1);

 

  则执行后str1="Lov",读取了4-1=3个字符,

 

  而如果用 

 

  fgets(str1,23,file1);

 

  则执行str1="Love ,I Have",读取了一行(包括行尾的' ',并自动加上字符串结束符'')。

二、fgets函数使用指针需要分配内存,或者使用数组。

三、用fgets(str,3,stdin)从屏幕读取数据时,比如屏幕输入:MSG 回车

第二次调用fgets(str,3,stdin)时,没有提示要输入数据了,原因很简单:回车以作为第二次调用结果输入进去,这样下次程序肯定出问题了

四、fgets和fputs是一对函数应配合使用,fgets会把换行符也读进来,在换行符后面加上"",fputs输出时会把换行符输出出来

附加:gets(p)和puts(p) 函数是一对应配合使用,gets会把输入的换行符去掉并在后面加上"",puts输出时会自动输出换行符.....

  由于gets()无法知道字符串s的大小,必须遇到换行字符或文件尾才会结束输入,因此容易造成缓冲溢出(1k)的安全性问题。建议使用fgets()取代。

注意了!!!!!!

 华为面试题 完成字符串拷贝可以使用 sprintf、strcpy 及 memcpy 函数,请问这些函数有什么区别,你喜欢使用哪个,为什么?
1.strcpy 函数操作的对象是字符串,完成从源字符串到目的字符串的拷贝功能。
2.snprintf 函数操作的对象不限于字符串:虽然目的对象是字符串,但是源对象可以是字符串、也可以是任意基本类型的数据。这个函数主要用来实现(字符串或基本数据类型)向字符串的转换功能。如果源对象是字符串,并且指定 %s 格式符,也可实现字符串拷贝功能。
3.memcpy 函数顾名思义就是内存拷贝,实现将一个内存块的内容复制到另一个内存块这一功能。内存块由其首地址以及长度确定。程序中出现的实体对象,不论是什么类型,其最终表现就是在内存中占据一席之地(一个内存区间或块)。因此,memcpy 的操作对象不局限于某一类数据类型,或者说可适用于任意数据类型,只要能给出对象的起始地址和内存长度信息、并且对象具有可操作性即可。鉴于 memcpy 函数等长拷贝的特点以及数据类型代表的物理意义,memcpy 函数通常限于同种类型数据或对象之间的拷贝,其中当然也包括字符串拷贝以及基本数据类型的拷贝。
 
对于字符串拷贝来说,用上述三个函数都可以实现,但是其实现的效率和使用的方便程度不同:
 1.strcpy 无疑是最合适的选择:效率高且调用方便
 2.snprintf 要额外指定格式符并且进行格式转化,麻烦且效率不高。
  3.memcpy 虽然高效,但是需要额外提供拷贝的内存长度这一参数,易错且使用不便;并且如果长度指定过大的话(最优长度是源字符串长度 + 1),还会带来性能的下降。其实 strcpy 函数一般是在内部调用 memcpy函数或者用汇编直接实现的,以达到高效的目的。因此,使用 memcpy 和 strcpy 拷贝字符串在性能上应该没有什么大的差别。

 

原文地址:https://www.cnblogs.com/siguoya/p/3464238.html