C语言内存使用的常见问题及解决之道

时间:2023-01-28 08:03:04 其他范文 收藏本文 下载本文

C语言内存使用的常见问题及解决之道(合集10篇)由网友“李大梦李小琼”投稿提供,以下是小编为大家汇总后的C语言内存使用的常见问题及解决之道,希望对大家有所帮助。

C语言内存使用的常见问题及解决之道

篇1:C语言内存使用的常见问题及解决之道

一 前言

本文所讨论的“内存”主要指(静态)数据区、堆区和栈区空间(详细的布局和描述参考《Linux虚拟地址空间布局》一文),数据区内存在程序编译时分配,该内存的生存期为程序的整个运行期间,如全局变量和static关键字所声明的静态变量。函数执行时在栈上开辟局部自动变量的储存空间,执行结束时自动释放栈区内存。堆区内存亦称动态内存,由程序在运行时调用malloc/calloc/realloc等库函数申请,并由使用者显式地调用free库函数释放。堆内存比栈内存分配容量更大,生存期由使用者决定,故非常灵活。然而,堆内存使用时很容易出现内存泄露、内存越界和重复释放等严重问题。

二 内存问题

2.1 数据区内存

2.1.1 内存越界

内存越界访问分为读越界和写越界。读越界表示读取不属于自己的数据,如读取的字节数多于分配给目标变量的字节数。若所读的内存地址无效,则程序立即崩溃;若所读的内存地址有效,则可读到随机的数据,导致不可预料的后果。写越界亦称“缓冲区溢出”,所写入的数据对目标地址而言也是随机的,因此同样导致不可预料的后果。

内存越界访问会严重影响程序的稳定性,其危险在于后果和症状的随机性。这种随机性使得故障现象和本源看似无关,给排障带来极大的困难。

数据区内存越界主要指读写某一数据区内存(如全局或静态变量、数组或结构体等)时,超出该内存区域的合法范围。

写越界的主要原因有两种:1) memset/memcpy/memmove等内存覆写调用;2) 数组下标超出范围。

复制代码

1 #define NAME_SIZE 5

2 #define NAME_LEN NAME_SIZE-1/*Terminator*/

3 char gszName[NAME_SIZE] = “Mike”;

4 char *pszName = “Jason”;

5 int main(void)

6 {

7  memset(gszName, 0, NAME_SIZE+1); //越界1

8  gszName[NAME_SIZE] = 0;     //越界2

9

10  if(strlen(pszName) <= NAME_SIZE) //越界3(注意'='号)

11    strcpy(gszName, pszName);

12

13  int dwSrcLen = strlen(pszName);

14  if(dwSrcLen < NAME_SIZE)

15    memcpy(gszName, pszName, dwSrcLen); //未拷贝结束符('\0')

16

17  return 0;

18 }

复制代码

使用数组时,经常发生下标“多1”或“少1”的操作,特别是当下标用于for循环条件表达式时。此外,当数组下标由函数参数传入或经过复杂运算时,更易发生越界。

复制代码

1 void ModifyNameChar(unsigned char ucCharIdx, char cModChar)

2 {

3  gszName[ucCharIdx] = cModChar; //写越界

4 }

5 int main(void)

6 {

7  ModifyNameChar(5, 'L');

8  unsigned char ucIdx = 0;

9  for(; ucIdx <= NAME_SIZE; ucIdx++) //'='号导致读越界

10    printf(“NameChar = %c\n”, gszName[ucIdx]);

11

12  return 0;

13 }

复制代码

对于重要的全局数据,可将其植入结构体内并添加CHK_HEAD和CHK_TAIL进行越界保护和检查:

复制代码

1 #define CODE_SIZE   4 //越界保护码的字节数

2 #if (1 == CODE_SIZE)

3  #define CODE_TYPE char

4  #define CHK_CODE  0xCC   //除0外的特殊值

5 #elif (2 == CODE_SIZE)

6  #define CODE_TYPE short

7  #define CHK_CODE  0xCDDC  //除0外的特殊值

8 #else

9  #define CODE_TYPE int

10  #define CHK_CODE  0xABCDDCBA //除0外的特殊值

11 #endif

12 #define CHK_HEAD  CODE_TYPE ChkHead;

13 #define CHK_TAIL  CODE_TYPE ChkTail;

14 #define INIT_CHECK(ptChkMem) do{ \

15  (ptChkMem)->ChkHead = CHK_CODE; \

16  (ptChkMem)->ChkTail = CHK_CODE; \

17 }while(0)

18 #define CHK_OVERRUN(ptChkMem) do{ \

19  if((ptChkMem)->ChkHead != CHK_CODE || (ptChkMem)->ChkTail != CHK_CODE) { \

20    printf(“[%s(%d)<%s>]Memory Overrun(ChkHead:0x%X,ChkTail:0x%X)!\n”, __FILE__, __LINE__, FUNC_NAME, \

21    (ptChkMem)->ChkHead, (ptChkMem)->ChkTail); \

22  } \

23 }while(0)

24 typedef struct{

25  CHK_HEAD;

26  char szName[NAME_SIZE];

27  CHK_TAIL;

28 }T_CHK_MEM;

29 T_CHK_MEM gtChkMem;

30 int main(void)

31 {

32  memset(>ChkMem, 0, sizeof(T_CHK_MEM));

33  INIT_CHECK(>ChkMem);

34

35  memset(>ChkMem, 11, 6);

36  CHK_OVERRUN(>ChkMem);

37  strcpy(gtChkMem.szName, “Elizabeth”);

篇2:C语言的变量的内存分配

先看一下两段代码:

char* toStr

{

char *s = “abcdefghijkl”;

return s;

}

int main()

{

cout << toStr() << endl;

return 0;

}

char* toStr()

{

char *s = “abcdefghijkl”;

return s;

}

int main()

{

cout << toStr() << endl;

return 0;

}和

[cpp] view plaincopyprint?

char* toStr()

{

char s[] = “abcdefghijkl”;

return s;

}

int main()

{

cout << toStr() << endl;

return 0;

}

char* toStr()

{

char s[] = “abcdefghijkl”;

return s;

}

int main()

{

cout << toStr() << endl;

return 0;

}

前一段代码打印出来是字符串,而后一段代码打印出来就是乱码,记得学C语言的时候讲到,字符串是被当做字符数组来处理的。所以字符数组名就相当于指向首地址的指针。那么

1. char *s = “abcdefghijkl”;

2. char s[] = “abcdefghijkl”;

这两种表达式似乎是一样的,可是为什么程序结果会不一样呢?原因就是没有对内存分配了解好。当然现在的C语言教材不会讲到的。

解释:

程序的意思比较简单,不用解释。

第一种表达式,指针s是局部变量,他的作用域是函数toStr内。它将其指向的地址返回,返回之后s即被销毁,庆幸s指向的地址被返回了回来。最终打印正确。

第二种表达式,那么我们会问第二种与第一种的区别在哪,为何错?原因就是第一种指针s虽然是局部变量,被分配在栈空间,作用域是函数内部,但其指向的内容“abcdefghijkl”是常量,被分配在程序的常量区。直到整个程序结束才被销毁。而第二种,s是一数组,分配到栈空间,“abcdefghijkl”作为数组各个元素被放到数组中,一旦函数退出,栈中这块内存就被释放。虽然返回一个地址,可是已经失去它的意义了。

通过以上例子,我们来学习学习内存分配的问题吧。

首先,需要搞清楚:变量的类型和它的存储类别是两个概念。

数据类型和内存管理没有直接的关系。

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

1、栈区(stack)—由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。

2、堆区(heap)—一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,呵呵。

3、全局区(静态区)(static),全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 程序结束后由系统释放。

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

5、程序代码区—存放函数体的二进制代码。

二、例子程序

这是一个前辈写的,非常详细

//main.cpp

int a = 0; //全局初始化区

char *p1; //全局未初始化区

main()

{

int b; //栈

char s[] = “abc”; //栈

char *p2; //栈

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

static int c =0;//全局(静态)初始化区

p1 = (char *)malloc(10);

p2 = (char *)malloc(20);//分配得来得10和20字节的区域就在堆区。

strcpy(p1, “123456”); //123456\\0放在常量区,编译器可能会将它与p3所指向的“123456”优化成一个地方,

}

//main.cpp

int a = 0; //全局初始化区

char *p1; //全局未初始化区

main()

{

int b; //栈

char s[] = “abc”; //栈

char *p2; //栈

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

static int c =0;//全局(静态)初始化区

p1 = (char *)malloc(10);

p2 = (char *)malloc(20);//分配得来得10和20字节的区域就在堆区。

strcpy(p1, “123456”); //123456\\0放在常量区,编译器可能会将它与p3所指向的“123456”优化成一个地方。

}

这下就对程序的内存分配理解更深入了吧。

其实包括其他编程语言,Java等,他们都有所谓的栈空间和堆空间以及常量区,我们经常写完程序之后发现莫名的错误,或者内存被慢慢吞噬,这都是这方面的原因。

以下是堆和栈的理论知识

2.1申请方式

stack: 由系统自动分配。 例如,声明在函数中一个局部变量 int b; 系统自动在栈中为b开辟空间

heap: 需要程序员自己申请,并指明大小,在c中malloc函数

如p1 = (char *)malloc(10);

在C++中用new运算符

如p2 = (char *)malloc(10);

但是注意p1、p2本身是在栈中的。

2.2 申请后系统的响应

栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。

堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,

会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。

2.3申请大小的限制

栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在 WINDOWS 下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。

堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。

所以在程序中自动变量数组(函数内部)不能很大,因为栈(这就是我们通常说的程序的堆栈段,大数组发生段溢出)的大小有限,而可以申请为全局变量,因为那是分配在静态区,大小不受限制。

2.4申请效率的比较:

栈由系统自动分配,速度较快。但程序员是无法控制的。

堆是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便.

另外,在WINDOWS下,最好的方式是用VirtualAlloc分配内存,他不是在堆,也不是在栈是直接在进程的地址空间中保留一快内存,虽然用起来最不方便。但是速度快,也最灵活

2.5堆和栈中的存储内容

栈: 在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。

当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。

堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排。

2.6存取效率的比较

char s1[] = “aaaaaaaaaaaaaaa”;

char *s2 = “bbbbbbbbbbbbbbbbb”;

aaaaaaaaaaa是在运行时刻赋值的;

而bbbbbbbbbbb是在编译时就确定的;

但是,在以后的存取中,在栈上的数组比指针所指向的字符串(例如堆)快。

比如:

#include

void main()

{

char a = 1;

char c[] = “1234567890”;

char *p =“1234567890”;

a = c[1];

a = p[1];

return;

}

对应的汇编代码

10: a = c[1];

00401067 8A 4D F1 mov cl,byte ptr [ebp-0Fh]

0040106A 88 4D FC mov byte ptr [ebp-4],cl

11: a = p[1];

0040106D 8B 55 EC mov edx,dword ptr [ebp-14h]

篇3:C语言宏定义使用技巧

写好C语言,漂亮的宏定义很重要,使用宏定义可以防止出错,提高可移植性,可读性,方便性 等等,下面列举一些成熟软件中常用得宏定义……

1,防止一个头文件被重复包含

#ifndef COMDEF_H

#define COMDEF_H

//头文件内容

#endif

2,重新定义一些类型,防止由于各种平台和编译器的不同,而产生的类型字节数差异,方便移植。

typedef unsigned char boolean; /* Boolean value type. */

typedef unsigned long int uint32; /* Unsigned 32 bit value */

typedef unsigned short uint16; /* Unsigned 16 bit value */

typedef unsigned char uint8; /* Unsigned 8 bit value */

typedef signed long int int32; /* Signed 32 bit value */

typedef signed short int16; /* Signed 16 bit value */

typedef signed char int8; /* Signed 8 bit value */

//下面的不建议使用

typedef unsigned char byte; /* Unsigned 8 bit value type. */

typedef unsigned short word; /* Unsinged 16 bit value type. */

typedef unsigned long dword; /* Unsigned 32 bit value type. */

typedef unsigned char uint1; /* Unsigned 8 bit value type. */

typedef unsigned short uint2; /* Unsigned 16 bit value type. */

typedef unsigned long uint4; /* Unsigned 32 bit value type. */

typedef signed char int1; /* Signed 8 bit value type. */

typedef signed short int2; /* Signed 16 bit value type. */

typedef long int int4; /* Signed 32 bit value type. */

typedef signed long sint31; /* Signed 32 bit value */

typedef signed short sint15; /* Signed 16 bit value */

typedef signed char sint7; /* Signed 8 bit value */

3,得到指定地址上的一个字节或字

#define MEM_B( x ) ( *( (byte *) (x) ) )

#define MEM_W( x ) ( *( (word *) (x) ) )

4,求最大值和最小值

#define MAX( x, y ) ( ((x) >(y)) ? (x) : (y) )

#define MIN( x, y ) ( ((x) < (y)) ? (x) : (y) )

5,得到一个field在结构体(struct)中的偏移量

#define FPOS( type, field )

/*lint -e545 */ ( (dword) &(( type *) 0)->field ) /*lint +e545 */

6,得到一个结构体中field所占用的字节数

#define FSIZ( type, field ) sizeof( ((type *) 0)->field )

7,按照LSB格式把两个字节转化为一个Word

#define FLIPW( ray ) ( (((word) (ray)[0]) * 256) + (ray)[1] )

8,按照LSB格式把一个Word转化为两个字节

#define FLOPW( ray, val )

(ray)[0] = ((val) / 256);

(ray)[1] = ((val) & 0xFF)

9,得到一个变量的地址(word宽度)

#define B_PTR( var ) ( (byte *) (void *) &(var) )

#define W_PTR( var ) ( (word *) (void *) &(var) )

10,得到一个字的高位和低位字节

#define WORD_LO(***) ((byte) ((word)(***) & 255))

#define WORD_HI(***) ((byte) ((word)(***) >>8))

11,返回一个比X大的最接近的8的倍数

#define RND8( x ) ((((x) + 7) / 8 ) * 8 )

12,将一个字母转换为大写

#define UPCASE( c ) ( ((c) >= 'a' && (c) <= 'z') ? ((c) - 0x20) : (c) )

13,判断字符是不是10进值的数字

#define DECCHK( c ) ((c) >= '0' && (c) <= '9')

14,判断字符是不是16进值的数字

#define HEXCHK( c ) ( ((c) >= '0' && (c) <= '9') ||

((c) >= 'A' && (c) <= 'F') ||

((c) >= 'a' && (c) <= 'f') )

15,防止溢出的一个方法

#define INC_SAT( val ) (val = ((val)+1 >(val)) ? (val)+1 : (val))

16,返回数组元素的个数

#define ARR_SIZE( a ) ( sizeof( (a) ) / sizeof( (a[0]) ) )

17,返回一个无符号数n尾的值MOD_BY_POWER_OF_TWO(X,n)=X%(2^n)

#define MOD_BY_POWER_OF_TWO( val, mod_by )

( (dword)(val) & (dword)((mod_by)-1) )

18,对于IO空间映射在存储空间的结构,输入输出处理

#define inp(port) (*((volatile byte *) (port)))

#define inpw(port) (*((volatile word *) (port)))

#define inpdw(port) (*((volatile dword *)(port)))

#define outp(port, val) (*((volatile byte *) (port)) = ((byte) (val)))

#define outpw(port, val) (*((volatile word *) (port)) = ((word) (val)))

#define outpdw(port, val) (*((volatile dword *) (port)) = ((dword) (val)))

19,使用一些宏跟踪调试

A N S I标准说明了五个预定义的宏名,

它们是:

_ L I N E _

_ F I L E _

_ D A T E _

_ T I M E _

_ S T D C _

如果编译不是标准的,则可能仅支持以上宏名中的几个,或根本不支持。记住编译程序

也许还提供其它预定义的宏名。

_ L I N E _及_ F I L E _宏指令在有关# l i n e的部分中已讨论,这里讨论其余的宏名。

_ D AT E _宏指令含有形式为月/日/年的串,表示源文件被翻译到代码时的日期。

源代码翻译到目标代码的时间作为串包含在_ T I M E _中。串形式为时:分:秒。

篇4:c语言 可变参数的使用

主要涉及函数

va_list ap 声明一个指针

va_start(ap,arg) 将ap指向到最后一个确定的参数arg

va_arg(ap,size) 将ap储存的地址加size,指向下一个参数

va_end(ap) 将ap赋值为NULL

#include#includeint sum(int, ...);int main(void){ printf(Sum of 10, 20 and 30 = %d, sum(3, 10, 20, 30) ); printf(Sum of 4, 20, 25 and 30 = %d, sum(4, 4, 20, 25, 30) ); return 0;}int sum(int num_args, ...){ int val = 0; va_list ap; //指向参数的指针 int i; va_start(ap, num_args); //将指针ap指向最后一个确定的参数 for(i = 0; i < num_args; i++) {val += va_arg(ap, int); //va_arg,将指针ap指向可变参数的首地址 } va_end(ap); //将指针ap赋值为NULL return val;}

运行结果:

10、20 和 30 的和 = 60

4、20、25 和 30 的和 = 79

篇5:C语言内存分配函数malloc、calloc和realloc

C语言中常用的内存分配函数有malloc、calloc和realloc等三个,其中,最常用的肯定是malloc,这里简单说一下这三者的区别和联系,

1、声明

这三个函数都在stdlib.h库文件中,声明如下:

void* realloc(void* ptr, unsigned newsize);

void* malloc(unsigned size);

void* calloc(size_t numElements, size_t sizeOfElement);

它们的功能大致类似,就是向操作系统请求内存分配,如果分配成功就返回分配到的内存空间的地址,如果没有分配成功就返回NULL。

2、功能

malloc(size):在内存的动态存储区中分配一块长度为“size”字节的连续区域,返回该区域的首地址。

calloc(n,size):在内存的动态存储区中分配n块长度为“size”字节的连续区域,返回首地址。

realloc(*ptr,size):将ptr内存大小增大或缩小到size。

需要注意的是realloc将ptr内存增大或缩小到size,这时新的空间不一定是在原来ptr的空间基础上,增加或减小长度来得到,而有可能(特别是在用realloc来增大ptr的内存空间的时候)会是在一个新的内存区域分配一个大空间,然后将原来ptr空间的内容拷贝到新内存空间的起始部分,然后将原来的空间释放掉。因此,一般要将realloc的返回值用一个指针来接收,下面是一个说明realloc函数的例子。

#include#includeint main(){ //allocate space for 4 integers int *ptr=(int *)malloc(4*sizeof(int)); if (!ptr) { printf(“Allocation Falure!\n”); exit(0); } //print the allocated address printf(“The address get by malloc is : %p\n”,ptr); //store 10、9、8、7 in the allocated space int i; for (i=0;i<4;i++) { ptr[i]=10-i; } //enlarge the space for 100 integers int *new_ptr=(int*)realloc(ptr,100*sizeof(int)); if (!new_ptr) { printf(“Second Allocation For Large Space Falure!\n”); exit(0); } //print the allocated address printf(“The address get by realloc is : %p\n”,new_ptr); //print the 4 integers at the beginning printf(“4 integers at the beginning is:\n”); for (i=0;i<4;i++) { printf(“%d\n”,new_ptr[i]); } return 0;}运行结果如下:

从上面可以看出,在这个例子中新的空间并不是以原来的空间为基址分配的,而是重新分配了一个大的空间,然后将原来空间的内容拷贝到了新空间的开始部分,

3、三者的联系

calloc(n,size)就相当于malloc(n*size),而realloc(*ptr,size)中,如果ptr为NULL,那么realloc(*ptr,size)就相当于malloc(size)。

篇6:C语言 字符串的内存拷贝处理函数

对于字符串来说,我们运用字符串中的str类别的函数,但是我们还有一些关于内存的拷贝函数,

C语言 字符串的内存拷贝处理函数

。他们操作的对象是内存,然后可以接受任何类型的数据进行拷贝。

这个是里面的memcpy,然后我们一起查看一下MSDN看一看他的原型:

void *memcpy(void *dest,const void *src,size_t count);

与strcpy不同的就是添加了第三个参数,确定操作的字节数,然后参数类型还有返回类型都是void*

,这表示他可以拷贝任意类型的数据。

然后我们看一下实现:

memcpy:

void *my_memcpy(void *str,const void *Dstr,int count) //从内存地址开始改变,并确定改变长度,所以用万能类型去接受{ char *pstr = (char *)str; char *pDstr = (char *)Dstr;assert((str!=NULL) && (Dstr != NULL)); if(str == Dstr) //位置相同情况下直接返回需要改变的 return (char *)Dstr; while(count-- >0){*pstr++ = *pDstr++;} return str;}

然后会出现一个问题,如果我们拷贝的数据中Dstr的起始位置在STR操作之间,那么在改变str时会出现副作用,将导致我们的拷贝结果不正确,所以我们应该考虑到会覆盖的情况,

在函数库中有一个memmove函数。

memmove:

void *my_memmove(void *pst,const void *Dpst,int size){ void *p = pst; char *pstA = (char *)pst; char *pstB = (char *)Dpst;assert((pst != NULL) &&(Dpst != NULL)); if(pstB

就是遇到被拷贝的空间起始处在拷贝空间中,将会遇到拷贝内存覆盖的现象。在这种情况下我们将考虑从尾部进行拷贝。所以进行了判断。

篇7:C语言如何决定使用那种整数类型

C语言如何决定使用那种整数类型

如果需要大数值(大于32, 767 或小于?32, 767), 使用long 型,否则, 如果空间很重要(如有大数组或很多结构), 使用short 型。除此之外, 就使用int 型。如果严格定义的溢出特征很重要而负值无关紧要, 或者你希望在操作二进制位和字节时避免符号扩展的'问题, 请使用对应的无符号类型。但是, 要注意在表达式中混用有符号和无符号值的情况。

尽管字符类型(尤其是无符号字符型) 可以当成“小” 整型使用, 但由于不可预知的符号扩展和代码增大有时这样做可能得不偿失,

使用无符号字符型有所帮助;

在选择浮点型和双精度浮点型时也有类似的权衡。但如果一个变量的指针必须为特定的类型时, 以上规则不再适用。

如果因为某种原因你需要声明一个有严格大小的变量, 确保象C99 的 那样用某种适当的typedef 封装这种选择。通常, 这样做唯一的好原因是试图符合某种外部强加的存储方案

篇8:C语言system使用教程:system函数用法解析

system函数功能强大,很多人用却对它的原理知之甚少先看linux版system函数的源码:

#include

#include

#include

#include

int system(const char * cmdstring)

{

pid_t pid;

int status;

if(cmdstring == NULL){

return (1);

}

if((pid = fork())<0){

status = -1;

}

else if(pid = 0){

execl(“/bin/sh”, “sh”, “-c”, cmdstring, (char *)0);

-exit(127); //子进程正常执行则不会执行此语句

}

else{

while(waitpid(pid, &status, 0) < 0){

if(errno != EINTER){

status = -1;

break;

}

}

}

return status;

}

分析一下原理估计就能看懂了:

当system接受的命令为NULL时直接返回,否则fork出一个子进程,因为fork在两个进程:父进程和子进程中都返回,这里要检查返回的pid,fork在子进程中返回0,在父进程中返回子进程的pid,父进程使用waitpid等待子进程结束,子进程则是调用execl来启动一个程序代替自己,execl(“/bin/sh”, “sh”, “-c”, cmdstring, (char*)0)是调用shell,这个shell的路径是/bin/sh,后面的字符串都是参数,然后子进程就变成了一个shell进程,这个shell的参数是cmdstring,就是system接受的参数,在windows中的shell是command,想必大家很熟悉shell接受命令之后做的事了。

再解释下fork的原理:当一个进程A调用fork时,系统内核创建一个新的进程B,并将A的内存映像复制到B的进程空间中,因为A和B是一样的,那么他们怎么知道自己是父进程还是子进程呢,看fork的返回值就知道,上面也说了fork在子进程中返回0,在父进程中返回子进程的pid。

windows中的情况也类似,就是execl换了个又臭又长的名字,参数名也换的看了让人发晕的,我在MSDN中找到了原型,给大家看看:

HINSTANCE ShellExecute(

HWND hwnd,

LPCTSTR lpVerb,

LPCTSTR lpFile,

LPCTSTR lpParameters,

LPCTSTR lpDirectory,

INT nShowCmd

);

用法见下:

ShellExecute(NULL, “open”, “c:\\a.reg”, NULL, NULL, SW_SHOWNORMAL);

你也许会奇怪 ShellExecute中有个用来传递父进程环境变量的参数 lpDirectory,linux中的execl却没有,这是因为execl是编译器的函数(在一定程度上隐藏具体系统实现),在linux中它会接着产生一个linux系统的调用 execve, 原型见下:

int execve(const char * file,const char **argv,const char **envp);

看到这里就会明白为什么system()会接受父进程的环境变量,但是用system改变环境变量后,system一返回主函数还是没变,

原因从system的实现可以看到,它是通过产生新进程实现的,从我的分析中可以看到父进程和子进程间没有进程通信,子进程自然改变不了父进程的环境变量。

使用了system函数就能执行dos指令。

#include

#include

xiaoyu()

{

char *a;

int n=0;

FILE *f;

f=fopen(“file.bat”,“w+”);/*新建一个批处理*/

if(f==NULL)

exit(1);

a=“echo”; /*DOS命令*/

for(n=65;n<=90;n++)/*大写A-Z*/

fprintf(f,“%s %c\n”,a,n);/*利用ASCII码输出A-Z,写出批处理*/

fclose(f);

system(“file.bat”);/*运行批处理*/

}

main()

{

char *string;

xiaoyu();

string=“echo C语言的system函数\n”;/*输出中文*/

system(string);

system(“pause”);/*程序暂停*/

}

C中可以使用DOS命令,以后编程通过调用DOS命令很多操作就简单多了。

篇9:c语言:sizeof的使用,其不是函数,是一个操作符关键

关于sizeof的使用,注意其不是函数,是一个操作符关键字

程序一:

#include void fun(int arr[10]){printf(“fun::%d\n”, sizeof(arr));//}int main(){int i = 10;short a = 0;int arr[10];fun(arr);printf(“%d\n”, sizeof(arr));//40 printf(“%d\n”, sizeof(a++));//2printf(“%d\n”, a);//0printf(“%d\n”, i);//10system(“pause”);return 0;}

结果:

fun::4

40

2

0

10

请按任意键继续. . .

程序二:

定义一个数组arr,输出arr和&arr的区别#include int main(){int n = 10;int arr[10] = {0};int *p = NULL;int (*q)[10] = NULL;printf(“%d\n”, sizeof(n));//4printf(“%d\n”, sizeof(int));//4printf(“%d\n”, sizeof(arr));//40 printf(“%d\n”, sizeof(&arr));//4printf(“%p\n”, arr);//00D4FACC &arr[0],它表示数组首元素的地址printf(“%p\n”, &arr);//00D4FACC,它表示数组的地址,这两个表示意义不同p = arr;q = &arr;printf(“p+1=%p\n”,p+1);//p+1=007DF958printf(“q+1=%p\n”,q+1);//q+1=007DF97Creturn 0;}

结果:

4

4

40

4

00F5F7C8

00F5F7C8

p+1=00F5F7CC

q+1=00F5F7F0

请按任意键继续. . .

篇10:cocos2dx下C++11正则表达式和android下C语言正则表达式的使用案例密码校验

/************************************************************************//* 密码校验*//* C++11下没有问题,但是C语言是有问题的 *//* 错误案例:^[a-zA-Z0-9~!@#$%^&*()_+`\-={}\[\]:;'?,.\/]{6,20}$ 正确案例:^[]a-zA-Z0-9~!@#$%^&*()_+`={}:;'?,./\[-]{6,20}$ 解答:最恶心的匹配有三个字符 []- - 如果出现在 [] 的开头或结尾,表示匹配字符 ’-’ ,例如 [^-abc] , [-abc] , [abc-] ,

cocos2dx下C++11正则表达式和android下C语言正则表达式的使用案例密码校验

。注意不能使用 ’’ 来转义 ] 可以出现在中括号中的第一个位置,例如 []abc] 或 [^]abc] [ 需要转义*/bool CommonFunc::CheckPasswordLegal(std::string strPassword, int lengthMin, int lengMax){ if (lengMax == 0) {#if (CC_TARGET_PLATFORM. == CC_PLATFORM_WIN32) return StringUtil::StringVerification(strPassword, StringUtil::format256(^[a-zA-Z0-9~!@#$%^&*()_+`\-={}\[\]:;'?,.\/]+$)); #endif #if (CC_TARGET_PLATFORM. == CC_PLATFORM_ANDROID||CC_TARGET_PLATFORM. == CC_PLATFORM_IOS) return StringUtil::StringVerification(strPassword, StringUtil::format256(^[]a-zA-Z0-9~!@#$%^&*()_+`={}:;'?,./\[-]+$)); #endif } else if (lengMax != 0) {#if (CC_TARGET_PLATFORM. == CC_PLATFORM_WIN32) return StringUtil::StringVerification(strPassword, StringUtil::format256(^[a-zA-Z0-9~!@#$%^&*()_+`\-={}\[\]:;'?,.\/]{%d,%d}$, lengthMin, lengMax)); #endif #if (CC_TARGET_PLATFORM. == CC_PLATFORM_ANDROID||CC_TARGET_PLATFORM. == CC_PLATFORM_IOS) return StringUtil::StringVerification(strPassword, StringUtil::format256(^[]a-zA-Z0-9~!@#$%^&*()_+`={}:;'?,./\[-]{%d,%d}$, lengthMin, lengMax)); #endif } return false;}bool StringUtil::StringVerification(std::string src, std::string regular){#if (CC_TARGET_PLATFORM. == CC_PLATFORM_WIN32) regex pattern(regular.c_str()); if ( !regex_match( src, pattern ) ) { return false; } return true;#endif #if (CC_TARGET_PLATFORM. == CC_PLATFORM_ANDROID||CC_TARGET_PLATFORM. == CC_PLATFORM_IOS) regex_t reg; int retval = regcomp(®, regular.c_str(), REG_EXTENDED | REG_NEWLINE); retval = regexec(®, src.c_str(), 0, NULL, 0); CCLOG(%s is %s, regular.c_str(), retval == 0 ? legal : illegal); if (retval == 0) { regfree(®); return true; } else { return false; }#endif}难受了我三天,正则表达式在不同的语言下差别还是有很多,特别是对需要转义的字符,

终于搞定了,C++11下的正则表达式是OK,正常转义。

C语言下的正则表达式转义是不行的,必须要按照规范书写,“-”在最后,“]”在最前,[需要转义。

唯品会 java 面试

《饭店管理知识》教案

学习外语的五大绝招

C语言宏定义使用技巧

关于电脑开机的问题解决

生物培养能力要落在实处

寒期社区服务社会实践报告计算机专业

计算机专业学生社会实践心得

各大公司口号

计算机社会实践报告

C语言内存使用的常见问题及解决之道
《C语言内存使用的常见问题及解决之道.doc》
将本文的Word文档下载到电脑,方便收藏和打印
推荐度:
点击下载文档

【C语言内存使用的常见问题及解决之道(合集10篇)】相关文章:

记叙文升级方法2022-04-30

计算机内网安全相关知识2022-10-13

计算机周记2023-02-03

高级Java面试题集合2023-09-27

计算机个人实习总结2022-12-23

计算机实习报告范文2022-12-05

计算机实习报告1000字2023-11-28

实习周记计算机2024-05-07

惯有嵌入式系统内存管理方案研究2023-07-08

计算机实习报告格式2022-05-08

点击下载本文文档