C语法基础

转载请注明出处:http://blog.csdn.net/mr_liabill/article/details/48751843  来自《LiaBin的博客》

内存分配

堆,栈,静态区,常量区

1、栈:是由编译器在需要时自动分配,不需要时自动清除的变量存储区。通常存放局部变量、函数参数等。

2、堆:由malloc等分配的内存块,用free来释放,程序员需要手动分配,手动释放,否则会造成内存泄漏

3、静态存储区:全局变量和静态变量被分配到同一块内存中。

4、常量存储区:这是一块特殊存储区,里边存放常量,不允许修改。

经典例子

int a = 0; 全局初始化区 
char *p1; 全局未初始化区 
main() 
{ 
   int b; 栈 
   char s[] = "123"; 栈 
   char *p2; 栈 
   char *p3 = "123456"; “123456\0”在常量区,p3在栈上。 
   static int c =0; 全局(静态)初始化区 
   p1 = (char *)malloc(10);  堆 
   p2 = (char *)malloc(20);  堆 
}

栈内存: 4个字节给b,3个字节给s,接下来分别给p2.p3分配8个字节地址

堆内存: (char *)malloc(10)+(char *)malloc(20) 总共分配30个字节内存地址

常量区: “123456”  注意默认应该还有'\0'

静态变量区: 4个字节给a,8个字节给p1,4个字节地址给c


注意事项:

1. 指针变量,内存分配8个地址。同时编译器并不为指针所指向的对象分配空间,只为分配指针本身的空间

2. 字符串默认有'\0'

3. 字符串初始化指针,此时字符串视为常量,放在常量区。如果初始化数组,则该字符串不能视为常量,放在栈中。

4. static修饰的局部变量,该自动变量变为静态变量

5. 静态变量如果未初始化,则默认为0。自动变量,随机一个值。


自动变量,静态变量

静态变量
在编译时就确定,全局变量或者代码块内部的static修饰变量,如果没初始化,默认为0

自动变量
随机一个初始值,在运行时才确认
int *p;
所以指针p可能指向内存中任何一个地址,所以如果此时×p,可能会异常退出

static修饰代码块之外的变量声明(也就是全局变量)或者函数定义时
那么从external改为internal,外部变为内部,也就是该变量或者函数只能在本文件中使用,由外部属性变为内部属性

static修饰代码块内部的变量声明
自动变量变为静态变量,就是在函数执行前创建,但是作用域不变,所以该变量只能在代码块中访问,代码块之外不能访问,虽然该变量一直存在!编译期间就已经确认了值,存储在静态区。如果未初始化,初始值为0

C语言中的字符串常量

字符串使用

字符串常量作为右值的情况下,一般都是返回char *指针。同时该字符串常量存储在字符串常量区,不可修改,举例如下

char *p = "test";    此时p指向字符串常量区“test"的首地址。如果*(p+1)=‘b'   -->异常,因为字符串常量不能修改

如果字符串用来初始化数组,那么不视为常量。

char a[]="test";     如果a不是静态变量,那么"test“存储在栈中,所以a[1]='b'  是正确的   完全等同于char a[]={'t','e','s','t','\0'}

	int i="A";
	int j="A";
	printf("i=0x%x\n",i);
	printf("j=0x%x\n",j);
打印:i=0x40077a    j=0x40077a
此时说明共用同一个常量区的”A“,int i = "A";  这样是合理的,此时变量i的内容就是”A“这个字符串的首地址,也说明字符串返回的是返回char *指针,注意跟int i = 'A'的区别

系统自动为“字符串”最后增加’\0’

    char name[]="test";
    printf("length of name:%s\n",name);
    printf("length of name:%d\n",strlen(name));
    printf("length of name:%d\n",sizeof(name));
输出:test,7,8

注意
1.sizeof  表示实际占用内存空间的大小,所以此时是5  strlen会一直查找直到出现'\0'终止,所以此时是4  printf("%s")也一样,直到出现'\0'

    char name[]={'t','e','s','t'};
    printf("length of name:%s\n",name);
    printf("length of name:%d\n",strlen(name));
    printf("length of name:%d\n",sizeof(name));
第一行打印,不可预料,因为"test"之后没法预料什么时候出现'\0',第二行同第一行,第三行打印4,表示该数组实际上占用4个内存单元

strlen  sizeof区别

    char name[10];
    char *np=name;
    printf("name:strlen=%d\n", strlen(name));
    printf("np:strlen=%d\n", strlen(np));
这取决于当程序运行时name中的数组存储的是什么值,如果是静态变量,因为数组元素初始化为'\0',那么输出结果铁定是0,如果是自动变量,恰好第一个元素存储的是’\0’,那么结果为0。如果不是,那么结果就难预料了。因为strlen从字符串第一个地址开始查找,直到‘\0’为止,注意不包括’\0’

但是sizeof在编译时就确定,不关心存储的内容具体是多少,只关心存储的字节是多少
所以sizeof(name) 结果为10  
如果sizeof(np)  注意此时输出的是8,因为此时表示的是指针变量的内存空间
如果sizeof(*np)  输出的却是1,因为表示的是np指向元素的内存空间,此时是char,所以输出1

以下方式可以得到数组长度
    int i[]={1,2};
    printf("%d",sizeof(i)/sizeof(i[0]));
输出:8

函数参数传递

按值传递

函数参数会复制拷贝实参到内存栈中,所以不管怎么修改都不会影响原值。注意如果这个参数是结构体,那么不要使用按值传递,因为会拷贝一个结构体,如果这个结构体很复杂的话,那么。。。。 

按指针传递

参数指针跟实参指针指向同一内存空间,如果形参指向的内容变了,那么实参内存也变了,如果形参指针只是指向不同内容了还是不会影响形参,参考如下代码

extern void test(char *);
int main(void) {
	char *mydata="binjing";
	printf("&mydata:0x%x\n",&mydata);
	printf("mydata email:0x%x\n",mydata);
	test(mydata);
	printf("mydata new value:%s\n",mydata);
	return 0;
}
void test(char t[]){
	printf("&t:0x%x\n",&t);
	printf("t email:0x%x\n",t);
	printf("old value:%s\n",t);
	t="zhoujing";
	printf("t new email:0x%x\n",t);
}
输出:

&mydata:0x579810c8

mydata email:0x4006fc

&t:0x579810a8

t email:0x4006fc

old value:binjing

t new email:0x40075f

mydata new value:binjing

此时原理一样,都是产生了一个临时变量(形参),临时变量是指针,这个变量的内容跟实参指针指向的内容一样,但是还是不同的符号,所以如果形参指针发生变化不会影响到实参,但如果形参指向的内容发生变化那么一定会影响到实参!

另一个值得关注的问题是, 如果作为函数参数,指针和数组是等价的


malloc函数

malloc函数使用

malloc函数用来动态在堆上分配内存,如果系统可用内存没有足够的内存分配给程序,那么将返回NULL,所以每次使用完之后记得判断是否为NULL,如果有足够的内存,那么返回void *,需要强制类型转换。同时不使用记得使用free释放

#include <stdio.h>
#include <stdlib.h>

int main(void) {
	char * cp = (char *) malloc(4 * sizeof(char));
	if (cp == NULL) {
		printf("内存不足");
	} else {
		printf("分配成功\n");
		printf("cp==%s\n", cp);//此时结果不可预料,因为堆内存中分配的4个内存单元内容不可预料
	}
	//释放cp指向的那块内存,这块内存就不能被该程序使用,CPU可能把这块内存分配给其他程序使用
	free(cp);
	//最好的方式就是释放完内存之后,把指针置为NULL,那么就不会出现野指针从而出现不可预料的结果
	//cp==NULL;
	//如果此时仍然访问cp指向的那块内存,则结果不可预料,此时cp也叫做野指针
	//printf("cp==%s\n", cp);
	return 0;
}

野指针

未初始化的指针可以称为野指针(wild pointer),但是野指针并不仅仅是未初始化的指针. 一般来说,野指针(dangling pointer, wild pointer)指向的是内存中无效的对象。

比如以下几个例子.最简单的例子,自动变量char *cp;此时cp未初始化,此时cp可以叫做野指针,因为可能指向内存中无效的对象

1. 如果有一段代码如下

char *dp = NULL;
{
     char c;
     dp = &c;
} /* c falls out of scope */
当程序走出大括号作用域后, 变量c的内存(在栈中)被系统回收,但是dp仍让指向那块内存,这块内存今后很有可能被系统用作其他用途,此时如果*dp,dp指向的内容将不会是你期待的内容,此处,dp可以称为野指针.

2. 第二种情况是用户自己分配内存,然后自己释放内存,释放内存后仍然引用指针指向的内容, 比如
#include <stdlib.h>
void func()
{
     char *dp = malloc(A_CONST);
     free(dp);         /* dp now becomes a dangling pointer */
     dp = NULL;        /* dp is no longer dangling */
}
在执行完free(dp)后,dp是一个野指针,因为他指向的内存已经被释放掉了,系统可能会把这段内存用于其他地方.这个时候,常用的一种做法是把这个指针设为NULL, 设为NULL的指针可以由程序员使用if (dp == NULL)语句坚持出来, 但是野指针无法监测.

3. 另外一种情况是指针指向函数中分配的栈内存,函数返回后,内存被回收,但是指针指向的地址没有改变,因此这个指针也是野指针.
extern int * mallocInt();
int main(void) {
	//此时ip指向自动变量i,此时被分配在栈中,mallocInt函数一过,所以该段内存也是非法的,此时ip也可称为野指针
	int *ip = mallocInt();
	printf("*ip==%d\n", *ip);
	return 0;
}

int * mallocInt() {
	int i = 100;
	int *ip = &i;
	return ip;
}

从以上三点可以总结出, 野指针其实就是就像一段无效的内存(被系统回收), 但是这个指针的值并不是NULL, 因此如果没有立刻把这个指针赋其他有效值,或者NULL值, 以后再次引用野指针就会出现segmentation faults这样的错误

NULL

内存位置为0,编译器确保没有任何变量,所以解引用*i会造成“Segment fault”,因为这个地址的内容没有任何变量特殊指针变量,不指向任何东西!要是一个指针变量为NULL,只需要给它赋值为0。NULL其实是一个int,值为0

int j = NULL;
printf("j=%d",j);  输出0

int * ip=NULL;
printf("ip=%d",*ip);  出现Segment fault

memset和memcpy函数使用

memset主要应用是初始化某个内存空间。
memcpy是用于copy源空间的数据到目的空间中。
strcpy用于字符串copy,遇到‘\0’,将结束。


void *memset(void *s, int ch, size_t n);
函数解释:将 s 中前 n 个字节 (typedef unsigned int size_t)用 ch 替换并返回 s 。
memset:作用是在一段内存块中填充某个给定的值,它是对较大的结构体或数组进行清零操作的一种最快方法.

char *p = malloc (10*sizeof(char));
//printf("*p=%c",*p);  //结果是不可预料的,可以通过memset初始化这段在堆内存中分配的内存
memset(p,’’,10);


void *memcpy(void *dest, const void *src, size_t n);
从源src所指的内存地址的起始位置开始拷贝n个字节到目标dest所指的内存地址的起始位置中
函数返回dest的值。

char *p = malloc (10);
char *name = (char *) malloc(11); 
// Assign some value to name
memcpy ( p,name,11); // Problem begins here
//memcpy 操作尝试将 11 个字节写到 p,而后者仅被分配了 10 个字节。那么p指针指向的10个地址空间,后面的第11个地址被非法改写,造成不可预料异常

数组,指针

声明,定义区别

定义:特殊的声明,创建对象并为对象分配内存

声明:而是描述其它地方创建的对象,不分配内存

所以

定义int temp[100];定义了100个int的数组

extern int temp[];声明上文的对象,此时不需要提供数组的长度,因为只是声明!

符号概念

任何一个变量在编译的时候都分配了一个内存地址,并把该记录保存在“编译器符号表”中(数组名-特殊存在)。

比如char c=’a’;此时内存单元745cfb6c的内容为’a’,在“符号表”中a代表的就是745cfb6c内存地址.

&操作符输出的就是该符号代表该内存单元地址,&c此时就是0x745cfb6c

注意跟*操作符的区别,*操作符只能用于指针变量,取指针实际指向的值

指针步长问题

int main(void) {
	int i[] = { 0x61, 0x62, 0x63, 0 };//0x61是'a'的asii码,然后依次是'b','c',0是'\0'的asii码
	//char i[] = { 0x61, 0x62, 0x63, 0 }; //这样定义才能得到正确的结果
	char *c = i;

	printf("sizeof(int)=%d\n", sizeof(int));
	printf("sizeof(char)=%d\n", sizeof(char));
	printf("c=%s\n", c);

	int j[] = { 0x61, 0x62, 0x63, 0 };
	printf("j=%s\n", j);
}
输出结果:

sizeof(int)=4
sizeof(char)=1
c=abc
j=a

c的内容就是i数组第一个元素的数组,但是因为cchar *指针,所以它下次查找会一个字长(sizeof(char)==1),所以在下一个字节中找到了’\0’,所以造成目前这个输出

同时也说明另外一个问题: %s 取得话就是 *(i+sizeof(char)) 来依次取得,直到出现 ’\0’

总结:*(cp+1)  这里的1并不只是一个内存单元,具体是几个字节是根据cp是什么类型的指针来决定的,所以能取到下一个元素

数组,指针区别

这里面的学问就大了去了,不深入分析,网上大量资料

1. 作为函数参数,数组和指针等价

2. 数组访问效率比指针要高,

int ar[]={1,2,3};

ar[2],*(ar+2)都为3  只是简单的取a+2地址的内容(根据int类型长度*2)

int *ap={1,2,3};

ap[2],*(ap+2)也都为3   先取ap地址内容,然后把该地址+2,取内容

所以说数组效率高一些

3. 数组名更多的时候视为符号,而不是指针

int main(void) {
	int a[]={1,2};
	int i = 1;
	int j = 2;
	int *z = &j;

	printf("&a= %x\n", &a);
	printf("&i= %x\n", &i);
	printf("&j= %x\n", &j);
	printf("&z= %x\n", &z);

	printf("a= %x\n", a);
	printf("i= %x\n", i);
	printf("j= %x\n", j);
	printf("z= %x\n", z);
	return EXIT_SUCCESS;
}

输出:

&a= 745cfb60

&i= 745cfb6c

&j= 745cfb68

&z= 745cfb58

a= 745cfb60

i= 1

j= 2

z=745cfb68
此时数组a和&a的输出结果是一样的,但是指针z输出的是指向内容的地址,&z输出的是z符号的地址


4. 以下情况需要留意

注意如果

char p[]=”binjing”;

另一个c文件中有如下代码:

extern char *p;

p[2]

此时将会出现“不可预料”的结果!因为如果此时p符号的内存地址为0x745cfb60。那么p符号地址中存储的内容就是’b’=62。所以如果p[2]=*(p+2)其实取得就是内存地址为62+2所存储的内容!!!!所以这个结果一定是不可预料的!


指针数组,数组指针

首先[]优先级大于*,()优先级无疑最大

数组指针(也称行指针)

定义 int (*p)[n];
()优先级高,首先说明p是一个指针,指向一个整型的一维数组,这个一维数组的长度是n,也可以说是p的步长。也就是说执行p+1时,p要跨过n个整型数据的长度。
如要将二维数组赋给一指针,应这样赋值:
int a[3][4];
int (*p)[4]; //该语句是定义一个数组指针,指向含4个元素的一维数组。
 p=a;        //将该二维数组的首地址赋给p,也就是a[0]或&a[0][0]
 p++;       //该语句执行过后,也就是p=p+1;p跨过行a[0][]指向了行a[1][]
所以数组指针也称指向一维数组的指针,亦称行指针。

指针数组

定义 int *p[n];
[]优先级高,先与p结合成为一个数组,再由int*说明这是一个整型指针数组,它有n个指针类型的数组元素。这里执行p+1是错误的,
这样赋值也是错误的:p=a;因为p是个不可知的表示,只存在p[0]、p[1]、p[2]...p[n-1],而且它们分别是指针变量可以用来存放变量地址。
但可以这样 *p=a; 这里*p表示指针数组第一个元素的值,a的首地址的值。
如要将二维数组赋给一指针数组:
int *p[3];
int a[3][4];
for(i=0;i<3;i++)
p[i]=a[i];
这里int *p[3] 表示一个一维数组内存放着三个指针变量,分别是p[0]、p[1]、p[2]
所以要分别赋值。

这样两者的区别就豁然开朗了,数组指针只是一个指针变量,似乎是C语言里专门用来指向二维数组的,它占有内存中一个指针的存储空间。步长为数组大小
指针数组是多个指针变量,以数组形式存在内存当中,占有多个指针的存储空间。
还需要说明的一点就是,同时用来指向二维数组时,其引用和用数组名引用都是一样的。
比如要表示数组中i行j列一个元素:
*(p[i]+j)、*(*(p+i)+j)、(*(p+i))[j]、p[i][j]


Typedef和define区别

    typedef  int * MyInt1;     //一个语句,如同定义变量一样定义了一种新类型

    #define  MyInt2 int*    //预处理,简单的文本替换,所以没有"分号",不是一个语句

    const MyInt1  my1;//my1表示指向int类型的常量指针,指针不能修改,指向的值可以修改,因为MyInt1,表示指向int的指针  等价于==int *const my1;

    const MyInt2  my2;//文本替换,const int* my2;所以此时my2是指针常量,指针可以修改,指向的值不能修改     等价于==const int* my2;


结构体Struct

结构指针

        typedef struct MyStruct {
		int *i;
		int j;
		char *home;
	} MyData;
	MyData *md1 ;//此时只是为md分配了内存空间,也就是八个字节
        MyData *md2=malloc(sizeof(MyData));//此时既为ms2分配内存,也分配结构体的内存
        MyData md3;//此时为md3分配一个结构体的内存

结构指针内存泄漏问题

typedef struct Person {
	char *name;
	int age;
	char *home;
} P;

int main(void) {
	P *p = (P*) malloc(sizeof(P));
	p->name = "liabin";
	p->home = (char*) malloc(10 * sizeof(char));
	printf("name=%s\n", p->name);
	free(p);
	return 0;
}

释放掉(P*) malloc(sizeof(P))之后,所以(char*) malloc(10 * sizeof(char))这段内存就没有指针指向它了,所以没法释放这段内存,造成内存泄漏。

注意malloc分配在堆上的内存都需要手动释放

解决方案:

free(p->home);
free(p);

每当释放结构化的元素,而该元素又包含指向动态分配的内存位置的指针时,应首先遍历子内存位置,并从那里开始释放,然后再遍历回父节点。

边界对齐

参考 点击打开链接

struct和union区别



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值