1。一点基础
int *p = NULL;
定义了 p是一个指针。
p这个指针的步长是4,也就是 ++p后,p的内存地址会增加4个byte。
p的初始值是NULL,NULL的定义一般是(void *)0或者0.
如果p是全局变量,那么内存单元在link时分配在静态存储区;如果是局部变量,那么内存单元在运行时分配在stack上。
定义指针时,编译器并不为指针所指向的对象分配空间,它只是分配指针本身的空间,除非在定义时同时赋给指针一个字符串常量进行初始化。
eg.
char *p = "breadfruit";
注意只有对字符串常量才是如此。另外,在ANSI C中,初始化指针时所创建的字符串常量被定义为只读。如果试图通过指针修改这个字符串的值,程序就
会出现未定义的行为。该字符串常量,一般存放在数据段,但在有些编译器中,存放在文本段,以防止它被修改。
与指针相反,由字符串常量初始化的数组是可以被修改的。
[Parts are referred from <<C Expert Programming>>]
2。常见用法
(1). 访问内存数据。
从底层的观点来看,一个指针就是一个内存地址。
(2). 动态分配时,储存分配出的内存地址。
eg.
char *p = NULL;
p = (char *)malloc(MAX_MEM_SIZE);
(3). 在函数中作为参数传入或传出,在函数内访问传入数据
eg.
int gid_name(char *name, gid_t *gid)
{
[...]
*gid = ptr->gid = gr->gr_gid;
return (0);
}
如上函數,返回值只用以标记错误条件。
使用指针类型参数有时也是为了避免函数调用时的大量数据copy,比如结构体,这时使用指针会更有效率。
eg.
static double diffsec(struct timeval *now, struct timeval *then)
{
return ((now->tv_sec - then->tv_sec)*1.0 + (now->tv_usec - then->tv_usec) / 1000000.0);
}
函数中通过指针传入的两个结构体的成员值计算结果。
像上面这种情况,当通过指针传入的参数不需被修改时,最后在前面添上const关键字,表示在函数作用域内,该参数只读。
(4). 数据成员访问
访问结构体的成员:p->f;
访问数组元素:
p = &a[0];
*(p + 3) = 0;
(5). 替代数组作为函数参数
在C中,当数组名作为函数参数时,传入的实际上是这个数组的第一个元素的地址, 即,注意当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针。这时,函数中任何对数组的改动将直接影响到该数组本身。
同样,C函数中return的数组名也会只返回一个指向该数组第一个元素的指针。所以,当从函数中返回一个数组时,要确保该数组不是从函数中定义的
局部变量,局部变量内存分配在栈上,否则,一旦函数退出,这个数组的内容会被覆盖,返回的数据会因此无效。避免这种情况的一个方法是将该数组定义为static。
eg.
char *inet_ntoa(struct in_addr ad)
{
unsigned long int s_ad;
int a, b, c, d;
static char addr[20];
int *p = NULL;
定义了 p是一个指针。
p这个指针的步长是4,也就是 ++p后,p的内存地址会增加4个byte。
p的初始值是NULL,NULL的定义一般是(void *)0或者0.
如果p是全局变量,那么内存单元在link时分配在静态存储区;如果是局部变量,那么内存单元在运行时分配在stack上。
定义指针时,编译器并不为指针所指向的对象分配空间,它只是分配指针本身的空间,除非在定义时同时赋给指针一个字符串常量进行初始化。
eg.
char *p = "breadfruit";
注意只有对字符串常量才是如此。另外,在ANSI C中,初始化指针时所创建的字符串常量被定义为只读。如果试图通过指针修改这个字符串的值,程序就
会出现未定义的行为。该字符串常量,一般存放在数据段,但在有些编译器中,存放在文本段,以防止它被修改。
与指针相反,由字符串常量初始化的数组是可以被修改的。
[Parts are referred from <<C Expert Programming>>]
2。常见用法
(1). 访问内存数据。
从底层的观点来看,一个指针就是一个内存地址。
(2). 动态分配时,储存分配出的内存地址。
eg.
char *p = NULL;
p = (char *)malloc(MAX_MEM_SIZE);
(3). 在函数中作为参数传入或传出,在函数内访问传入数据
eg.
int gid_name(char *name, gid_t *gid)
{
[...]
*gid = ptr->gid = gr->gr_gid;
return (0);
}
如上函數,返回值只用以标记错误条件。
使用指针类型参数有时也是为了避免函数调用时的大量数据copy,比如结构体,这时使用指针会更有效率。
eg.
static double diffsec(struct timeval *now, struct timeval *then)
{
return ((now->tv_sec - then->tv_sec)*1.0 + (now->tv_usec - then->tv_usec) / 1000000.0);
}
函数中通过指针传入的两个结构体的成员值计算结果。
像上面这种情况,当通过指针传入的参数不需被修改时,最后在前面添上const关键字,表示在函数作用域内,该参数只读。
(4). 数据成员访问
访问结构体的成员:p->f;
访问数组元素:
p = &a[0];
*(p + 3) = 0;
(5). 替代数组作为函数参数
在C中,当数组名作为函数参数时,传入的实际上是这个数组的第一个元素的地址, 即,注意当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针。这时,函数中任何对数组的改动将直接影响到该数组本身。
同样,C函数中return的数组名也会只返回一个指向该数组第一个元素的指针。所以,当从函数中返回一个数组时,要确保该数组不是从函数中定义的
局部变量,局部变量内存分配在栈上,否则,一旦函数退出,这个数组的内容会被覆盖,返回的数据会因此无效。避免这种情况的一个方法是将该数组定义为static。
eg.
char *inet_ntoa(struct in_addr ad)
{
unsigned long int s_ad;
int a, b, c, d;
static char addr[20];
s_ad = ad.s_addr;
d = s_ad % 256;
s_ad /= 256;
c = s_ad % 256;
s_ad /= 256;
b = s_ad % 256;
a = s_ad / 256;
sprintf(addr, "%d.%d.%d.%d", a, b, c, d);
return addr;
}
如上函数,如果不把数组addr声明为static,当函数inet_ntoa() return时,addr数组的内容会无效。
(6). 函数指针
C不允许函数作为参数传入其他函数中,但允许传递一个函数指针给另一个函数。
(7). 作为别名
eg.
struct output output = {NULL, 0, NULL, OUTBUFSIZ, 1, 0};
[...]
struct output *out1 = &output;
在上例中,指针变量out1可以用在使用变量output的地方。
一般这样使用有三个原因:
i. 提高效率
如果一个数据结构比较大,那么在某些场合,比如赋值语句中使用指针会避免对整个结构体的拷贝。
static struct termios cbreakt, rawt, *curt;
[...]
curt = useraw ? &rawt : &cbreakt;
ii. 访问static的数据
一个指针常用来指向不同的静态数据,
eg.
char *s;
[...]
s = *(opt->bval) ? "True" : "False";
iii. 在不同的函数中访问同一个全局数据
eg.
static long rntb[32] =
{
3, 0x9a319039, 0x32d9c024, 0x9b663182, 0x5da1f342,
[...]
};
d = s_ad % 256;
s_ad /= 256;
c = s_ad % 256;
s_ad /= 256;
b = s_ad % 256;
a = s_ad / 256;
sprintf(addr, "%d.%d.%d.%d", a, b, c, d);
return addr;
}
如上函数,如果不把数组addr声明为static,当函数inet_ntoa() return时,addr数组的内容会无效。
(6). 函数指针
C不允许函数作为参数传入其他函数中,但允许传递一个函数指针给另一个函数。
(7). 作为别名
eg.
struct output output = {NULL, 0, NULL, OUTBUFSIZ, 1, 0};
[...]
struct output *out1 = &output;
在上例中,指针变量out1可以用在使用变量output的地方。
一般这样使用有三个原因:
i. 提高效率
如果一个数据结构比较大,那么在某些场合,比如赋值语句中使用指针会避免对整个结构体的拷贝。
static struct termios cbreakt, rawt, *curt;
[...]
curt = useraw ? &rawt : &cbreakt;
ii. 访问static的数据
一个指针常用来指向不同的静态数据,
eg.
char *s;
[...]
s = *(opt->bval) ? "True" : "False";
iii. 在不同的函数中访问同一个全局数据
eg.
static long rntb[32] =
{
3, 0x9a319039, 0x32d9c024, 0x9b663182, 0x5da1f342,
[...]
};
static long *fptr = &rntb[4];
[...]
[...]
void srrandom(int x)
{
[...]
fptr = &state[rand_sep];
[...]
}
{
[...]
fptr = &state[rand_sep];
[...]
}
long rrandom()
{
[...]
*fptr += *rptr;
i = (*fptr >> 1) & 0x7fffffff;
{
[...]
*fptr += *rptr;
i = (*fptr >> 1) & 0x7fffffff;
}
本例中,fptr被初始化指向一个随机数种子表,然后在函数srrandom()中再次被赋值,最后,在rrandom函数中被用来修改所指向的数据。
本例中,fptr被初始化指向一个随机数种子表,然后在函数srrandom()中再次被赋值,最后,在rrandom函数中被用来修改所指向的数据。
(8). 处理string, 如strlen函数:
size_t strlen(const char *str)
{
register const char *s;
size_t strlen(const char *str)
{
register const char *s;
for (s = str; *s; ++s) ;
return(s - str);
}
(9). 直接内存访问
既然指针本质上表现为一个内存地址,那么在底层代码中会使用指针去访问特定硬件的内存地址。
static void vid_wrchar(char c)
{
volatile unsigned short *video = NULL;
return(s - str);
}
(9). 直接内存访问
既然指针本质上表现为一个内存地址,那么在底层代码中会使用指针去访问特定硬件的内存地址。
static void vid_wrchar(char c)
{
volatile unsigned short *video = NULL;
video = (unsigned short *)(0xe08b8000) +
vid_ypos * 80 + vid_xpos;
*video = (*video & 0xff00) | 0x0f00 | (unsigned short)c;
}
注意,现代操作系统一般不允许直接硬件访问,所以一般这样的代码只会出现在嵌入式系统或者内核和驱动程序中。
vid_ypos * 80 + vid_xpos;
*video = (*video & 0xff00) | 0x0f00 | (unsigned short)c;
}
注意,现代操作系统一般不允许直接硬件访问,所以一般这样的代码只会出现在嵌入式系统或者内核和驱动程序中。
3。 使用规则建议
这些规则主要是为了防止内存泄漏的
(1). 用malloc申请内存之后,应该立即检查指针值是否为NULL。防止使用指针值为NULL 的内存。
char *p = (char *)malloc(USER_DEFINE_SIZE);
if (NULL == p)
{
/* Exception Handle */
}
(2). 为数组和动态内存赋初值。防止将未被初始化的内存作为右值使用。
For Array:
int aVar[100] = { 0 };
这些规则主要是为了防止内存泄漏的
(1). 用malloc申请内存之后,应该立即检查指针值是否为NULL。防止使用指针值为NULL 的内存。
char *p = (char *)malloc(USER_DEFINE_SIZE);
if (NULL == p)
{
/* Exception Handle */
}
(2). 为数组和动态内存赋初值。防止将未被初始化的内存作为右值使用。
For Array:
int aVar[100] = { 0 };
For Dynamic allocate memory:
char *p = NULL; /* Initial */
p = (char *)malloc(USER_DEFINE_SIZE); /* Malloc buffer */
if (NULL == p)
{
/* Exception Handle */
}
memset(p, 0, USER_DEFINE_SIZE);
(3). 避免数组或指针的下标越界,特别要当心发生“多1”或者“少1”操作。
(4). 动态内存的申请与释放必须配对,防止内存泄漏。
For free allocate memory:
if (NULL != p)
{
free(p);
}
(5). 用free释放了内存之后,立即将指针设置为NULL,防止产生“野指针”。
char *p = NULL; /* Initial */
p = (char *)malloc(USER_DEFINE_SIZE); /* Malloc buffer */
if (NULL == p)
{
/* Exception Handle */
}
memset(p, 0, USER_DEFINE_SIZE);
(3). 避免数组或指针的下标越界,特别要当心发生“多1”或者“少1”操作。
(4). 动态内存的申请与释放必须配对,防止内存泄漏。
For free allocate memory:
if (NULL != p)
{
free(p);
}
(5). 用free释放了内存之后,立即将指针设置为NULL,防止产生“野指针”。
[Reference from <<High Quality C/C++ Programming Guide>>]