之前写了一篇<<**作为C函数参数>>的文章。 今天又看到这个样的代码。 不过, 比之前的多一点需要注意的地方
在glibc/login/login.c代码里:
static int
tty_name (int fd, char **tty, size_t buf_len)
{
.....((被切掉).....
if (rv == 0)
/*
这里 *tty = buf已经修改了tty这个变量指向的空间.
为什么不在改变前把传入的空间free掉呢?如:
如果不free掉, 使用这个函数前需要有另外一个变量引用这块空间
不用调用free, 是因为login里, _tty是一个局部变量, 会在函数退出后自己释放
*/
*tty = buf; /* Return buffer to the user. */
else if (buf != *tty)
free (buf); /* Free what we malloced when returning an error. */
return rv;
}
作者还是清楚的:
#ifdef PATH_MAX
char _tty[PATH_MAX + UT_LINESIZE];
#else
char _tty[512 + UT_LINESIZE];
#endif
char *tty = _tty;
.....
found_tty = tty_name (STDIN_FILENO, &tty, sizeof (_tty));
看出, 有两个变量指向那块空间. 还是数组, 安全得很呢.
因此, 如果**作为参数时:
1. 如果在函数内, 改变了**指向的内存, 这时, **不是指向通过malloc得到的内存是安全的
2. 否则, 请确保还有其它变量引用之
如:
int len = 100;
char *name = (char *)malloc( len );
char * other = name;
tty_name (1, &name, len)
或者是:
char name[100];
char *new = name;
tty_name (1, &new, sizeof( name ));
这种做法:
char name[100];
char *new = name;
可以像malloc一样得到内存, 但是它比较安全: 会自动根据变量的作用范围释放使用的内存.
这种细节, 处理又麻烦, 不处理又不对. 纠结.... 需要长期的code能力.
磨刀不误砍柴工
切记: 数据要比编程逻辑更容易驾驭(The Art of Unix Programming )
Thursday, January 21, 2010
Wednesday, January 20, 2010
man手册里的section 2和section 3
一直对section 2 和 section 3两个段的API分得不是很开.
section 2和3的说明:
2 System calls (functions provided by the kernel)
3 Library calls (functions within program libraries)
当然, 本人是分得到system calls和library calls的.
只是这两者的API有部分是一样的.
可以man 2 setgid和man 3 setgid. 可以发现, 差不多是一个屁样了.
3的存在, 是因为glibc需要有posix标准的一套API. 如glibc的setgid函数其实就是调用了system call的setgid API.
往往对section 2和3的API出现混淆. 举个例子吧:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
int main()
{
int fd = open("/tmp", O_DIRECTORY|O_RDONLY);
if(fd >=0)
printf("sucess\n");
else
printf("no sucess\n");
}
编译时使用: gcc /tmp/c2.c -D _GNU_SOURCE -o /tmp/a.out
这时的open是使用system call的open, 可以打开一个文件夹的, 手册是man 2 open里说的内容.
如果不用_GNU_SOURCE这个宏的话, 就是man 3 open里说的内容
对特同名这种函数, 使用了宏去控制
section 2和3的说明:
2 System calls (functions provided by the kernel)
3 Library calls (functions within program libraries)
当然, 本人是分得到system calls和library calls的.
只是这两者的API有部分是一样的.
可以man 2 setgid和man 3 setgid. 可以发现, 差不多是一个屁样了.
3的存在, 是因为glibc需要有posix标准的一套API. 如glibc的setgid函数其实就是调用了system call的setgid API.
往往对section 2和3的API出现混淆. 举个例子吧:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
int main()
{
int fd = open("/tmp", O_DIRECTORY|O_RDONLY);
if(fd >=0)
printf("sucess\n");
else
printf("no sucess\n");
}
编译时使用: gcc /tmp/c2.c -D _GNU_SOURCE -o /tmp/a.out
这时的open是使用system call的open, 可以打开一个文件夹的, 手册是man 2 open里说的内容.
如果不用_GNU_SOURCE这个宏的话, 就是man 3 open里说的内容
对特同名这种函数, 使用了宏去控制
取大文件最后N行数据
取大文件最后N行数据, 这个问题, 如果是我想的话, 就直接往'\n'字符里去想了:
1. 使用seek, 从后面读一块数据出.
2. 数'\n'的个数.
看到人家这样做:
import os
def tail (name, n_line):
lines = []
f = file (name)
n = 1
while len(lines) <= n_line:
try:
// -n_line * 80 是一个大概猜测的一块数据长度
f.seek (-n_line * 80 * n, os.SEEK_END)
// 当 len( lines ) > n_line 才跳出循环, 其中, lines[0]行的数据不一定是完整的
// 这代码的好处就是当最小条件跳出循环时, len( lines ) ==4 . 就算lines[0]是不完整的数据也没有关系. 因为只想取len(lines) - 1 行的数据.
lines = f.readlines()
n = n + 1
except:
lines = f.seek (0, os.SEEK_SET)
lines = f.readlines ()
break
return lines[-n_line:]
1. 使用seek, 从后面读一块数据出.
2. 数'\n'的个数.
看到人家这样做:
import os
def tail (name, n_line):
lines = []
f = file (name)
n = 1
while len(lines) <= n_line:
try:
// -n_line * 80 是一个大概猜测的一块数据长度
f.seek (-n_line * 80 * n, os.SEEK_END)
// 当 len( lines ) > n_line 才跳出循环, 其中, lines[0]行的数据不一定是完整的
// 这代码的好处就是当最小条件跳出循环时, len( lines ) ==4 . 就算lines[0]是不完整的数据也没有关系. 因为只想取len(lines) - 1 行的数据.
lines = f.readlines()
n = n + 1
except:
lines = f.seek (0, os.SEEK_SET)
lines = f.readlines ()
break
return lines[-n_line:]
[] 操作符用于函数参数
glibc/locale/setlocale.c文件里
113 /* Construct a new composite name. */
114 static char *
115 new_composite_name (int category, const char *newnames[__LC_LAST])
这种代码奶奶的还真没有看过. 一般都是直接使用指针做参数的. 这里使用了 数组的形式 做参数. 有什么好处呢?
朋友给出了一个简单的例子说明了道理:
#include <stdio.h>
void test(char *p[2])
{
puts(p[1]);
}
int main()
{
char *p[2] = {"hello\0","world\0"};
test(p);
}
输出为:
world
之所以使用数组的形式传参, 主要是为了方便地址偏移方便. 如果不是这样的话, 偏移时需要计算一下, 自己的例子说明:
void test(char **p)
{
puts(*(p + 1));
}
int main()
{
char *p[2] = {"hello\0","world\0"};
test(p);
}
重点被颜色的文字标出
顺便来一个整数偏移:
void test(char **p)
{
// convert pointer to int
int number = (int )p;
// convert int to pointer
// 可以看出, 指针变量加1是偏移4bytes, 如果使用整数的话, 还需要自己计算
p = (char **) (number + 4);
puts(*(p));
}
int main()
{
char *p[2] = {"hello\0","world\0"};
test(p);
}
113 /* Construct a new composite name. */
114 static char *
115 new_composite_name (int category, const char *newnames[__LC_LAST])
这种代码奶奶的还真没有看过. 一般都是直接使用指针做参数的. 这里使用了 数组的形式 做参数. 有什么好处呢?
朋友给出了一个简单的例子说明了道理:
#include <stdio.h>
void test(char *p[2])
{
puts(p[1]);
}
int main()
{
char *p[2] = {"hello\0","world\0"};
test(p);
}
输出为:
world
之所以使用数组的形式传参, 主要是为了方便地址偏移方便. 如果不是这样的话, 偏移时需要计算一下, 自己的例子说明:
void test(char **p)
{
puts(*(p + 1));
}
int main()
{
char *p[2] = {"hello\0","world\0"};
test(p);
}
重点被颜色的文字标出
顺便来一个整数偏移:
void test(char **p)
{
// convert pointer to int
int number = (int )p;
// convert int to pointer
// 可以看出, 指针变量加1是偏移4bytes, 如果使用整数的话, 还需要自己计算
p = (char **) (number + 4);
puts(*(p));
}
int main()
{
char *p[2] = {"hello\0","world\0"};
test(p);
}
**作为C函数参数
C的指针一旦多层. 读代码是相当难理解.
glibc/string/argz-ctsep.c里:
26 error_t
27 __argz_create_sep (const char *string, int delim, char **argz, size_t *len)
这个参数有一个char **argz, 很难理解是什么意思.
后面想通了这种做法意义.
1. 指针: 期待函数可以对指向的内存进行修改, 从而得到返回值. (除指针使用const修饰)
2. 指针的指针: 期待函数返回指向一块内存的地址.
举个例子
指针的情况:
extern int change( char *buffer);
char *buffer = (char *)malloc(100);
change(buffer);
* change函数会修改buffer内存
指针的指针的情况:
extern int change( char **buffer);
char *buffer = NULL;
change( &buffer );
* change函数会返回一块已经被修改的内存.
再看一组函数:
1. char *ttyname(int fd);
2. int ttyname_r(int fd, char *buf, size_t buflen);
参数是char *buf。 可以猜测。 ttyname_r会对一块已经申请的内存进行修改。因为内存在函数体里无法知道长度, 所以就有size_t buflen参数。因此使用时是这样的:
size_t buflen = 100;
char *buffer = (char *) malloc( buflen ); #这是一定事先申请的
if ( ttyname_r ( 1, buffer, buflen ) ){
...do_you_want....
}
如果 glibc是提供了一个这样的函数: int ttyname_r(int fd, char **buf);
那么使用时就可以:
char *buffer=NULL;
if ( ttyname_r ( 1, &buffer ) ){
...do_you_want....
}
这样感觉传少了参数, 还少打代码.
但是用法没有绝对的! 这样总结只是为了看代码是容易有方法
glibc/string/argz-ctsep.c里:
26 error_t
27 __argz_create_sep (const char *string, int delim, char **argz, size_t *len)
这个参数有一个char **argz, 很难理解是什么意思.
后面想通了这种做法意义.
1. 指针: 期待函数可以对指向的内存进行修改, 从而得到返回值. (除指针使用const修饰)
2. 指针的指针: 期待函数返回指向一块内存的地址.
举个例子
指针的情况:
extern int change( char *buffer);
char *buffer = (char *)malloc(100);
change(buffer);
* change函数会修改buffer内存
指针的指针的情况:
extern int change( char **buffer);
char *buffer = NULL;
change( &buffer );
* change函数会返回一块已经被修改的内存.
再看一组函数:
1. char *ttyname(int fd);
2. int ttyname_r(int fd, char *buf, size_t buflen);
参数是char *buf。 可以猜测。 ttyname_r会对一块已经申请的内存进行修改。因为内存在函数体里无法知道长度, 所以就有size_t buflen参数。因此使用时是这样的:
size_t buflen = 100;
char *buffer = (char *) malloc( buflen ); #这是一定事先申请的
if ( ttyname_r ( 1, buffer, buflen ) ){
...do_you_want....
}
如果 glibc是提供了一个这样的函数: int ttyname_r(int fd, char **buf);
那么使用时就可以:
char *buffer=NULL;
if ( ttyname_r ( 1, &buffer ) ){
...do_you_want....
}
这样感觉传少了参数, 还少打代码.
但是用法没有绝对的! 这样总结只是为了看代码是容易有方法
Sunday, January 17, 2010
string.h里的函数的maxlen限制
maxlen是指最大长度。 第一次注意到maxlen这个参数是在:
size_t __strnlen (const char *str, size_t maxlen)
C里, 对内存的操作常常是一个循环, 针对于字符串的话, 循环的退出条件常常是: 一个特定的字符
1. 如'\0'
2. strchr的第二个参数
这样, 在历遍这块内存时, 需要程序员明白一点: 自己是否确认1和2的字符串在你操作的内存中?
否则, 这将是很危险的. 举个例子:
void main(int argc, char *argv){
char a[10], b[10];
memset(a, 'b', 10);
a[5] = '\0';
//这里证明a的址址比b的高
printf("a - b = %d\n", (int)(a - b));
//b长度就会有问题
printf("strlen(a): %d, strlen(b): %d\n", strlen(a), strlen(b));
//应该使用strnlen函数!, strlen和strnlen的区别就在于maxlen
printf("strnlen(a): %d, strnlen(b): %d\n", strnlen(a, sizeof(a)), strnlen(b, sizeof(b)));
}
输出:
a - b = 10
strlen(a): 5, strlen(b): 15
strnlen(a): 5, strnlen(b): 10
如果在使用strlen的同时, 使用memset函数, 将会是灾难性的
按照这一种考虑, 把glibc/string下的函数分成如下
* optimize的意思是被多个字节一起操作, 或者可以被compiler优化
PS:: 为了安全, 还是有mexlen参数的函数靠谱, 再者, 有maxlen的函数会快
size_t __strnlen (const char *str, size_t maxlen)
C里, 对内存的操作常常是一个循环, 针对于字符串的话, 循环的退出条件常常是: 一个特定的字符
1. 如'\0'
2. strchr的第二个参数
这样, 在历遍这块内存时, 需要程序员明白一点: 自己是否确认1和2的字符串在你操作的内存中?
否则, 这将是很危险的. 举个例子:
void main(int argc, char *argv){
char a[10], b[10];
memset(a, 'b', 10);
a[5] = '\0';
//这里证明a的址址比b的高
printf("a - b = %d\n", (int)(a - b));
//b长度就会有问题
printf("strlen(a): %d, strlen(b): %d\n", strlen(a), strlen(b));
//应该使用strnlen函数!, strlen和strnlen的区别就在于maxlen
printf("strnlen(a): %d, strnlen(b): %d\n", strnlen(a, sizeof(a)), strnlen(b, sizeof(b)));
}
输出:
a - b = 10
strlen(a): 5, strlen(b): 15
strnlen(a): 5, strnlen(b): 10
如果在使用strlen的同时, 使用memset函数, 将会是灾难性的
按照这一种考虑, 把glibc/string下的函数分成如下
| no maxlen | optimize | maxlen | optimize | information | |
| copy | |||||
| strcpy | No | strncpy | Yes | strncpy(s1, s2, maxlen) 如果maxlen > strlen(s2), 那么 剩下的都用'\0'填充. 使用strcpy最后一个也为'\0' | |
| memset | Yes | ||||
| memcpy | Yes | ||||
| memccpy | No | memccpy(dest, src, '\0', maxlen)与strncp差不多,但是最后只有一个'\0' | |||
| mempcpy | Yes | 与memcpy相似 | |||
| duplicate | |||||
| strdup | Yes | strndup | 两者的区别是 调用strlen或者 调用strnlen 最后都调用 memcpy 最后byte为'\0' | ||
| find | |||||
| strchr | Yes | memchr | Yes | ||
| strrchr | Yes | memrchr | Yes | strrchr调用strchr | |
| strchrnull | Yes | ||||
| strstr | Yes | memmem | Yes | using a linear algorithm with a smaller coefficient. | |
| strcasestr | Yes | ||||
| length | |||||
| strlen | Yes | strnlen | Yes | ||
| compare | |||||
| strcmp | No | strncmp | Yes | ||
| memcmp | Yes | ||||
| strcasecmp | No | strncase | No | ||
| concatenate | |||||
| strcat | No | strncat | Yes | ||
| move | |||||
| memmove | |||||
| set | |||||
| strspn | |||||
| strcspn | |||||
| other | |||||
| strfry | |||||
| strxfrm |
* optimize的意思是被多个字节一起操作, 或者可以被compiler优化
PS:: 为了安全, 还是有mexlen参数的函数靠谱, 再者, 有maxlen的函数会快
Friday, January 15, 2010
NULL, 0x00, '\0'怎么看都是一个东西
void main(int argc, char *argv){
char a = '\0';
char b = (char ) 0x00;
char c = (char )NULL;
if ( a == b) printf("OK\n");
if ( a == c) printf("OK\n");
}
输出結果为:
OK
OK
只是NULL对compiler来说, 会测试变量的类型, 如:
char c = NULL;
会报: warning: initialization makes integer from pointer without a cast
如果不信息(char )NULL和 NULL是相同的数值的话, 可以使用:
grep -r "define NULL"
可以看到:
define NULL (0)
define NULL 0
define NULL ((void*)0)
char a = '\0';
char b = (char ) 0x00;
char c = (char )NULL;
if ( a == b) printf("OK\n");
if ( a == c) printf("OK\n");
}
输出結果为:
OK
OK
只是NULL对compiler来说, 会测试变量的类型, 如:
char c = NULL;
会报: warning: initialization makes integer from pointer without a cast
如果不信息(char )NULL和 NULL是相同的数值的话, 可以使用:
grep -r "define NULL"
可以看到:
define NULL (0)
define NULL 0
define NULL ((void*)0)
Subscribe to:
Posts (Atom)