磨刀不误砍柴工

切记: 数据要比编程逻辑更容易驾驭(The Art of Unix Programming )

Thursday, January 21, 2010

**作为C函数参数(2)

之前写了一篇<<**作为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能力.

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里说的内容

对特同名这种函数, 使用了宏去控制


取大文件最后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:]

[] 操作符用于函数参数

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);
}


**作为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....

}

这样感觉传少了参数, 还少打代码.

但是用法没有绝对的! 这样总结只是为了看代码是容易有方法

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下的函数分成如下


no maxlen
optimizemaxlen
optimizeinformation
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.

strcasestrYes



length






strlen
Yes
strnlen
Yes

compare






strcmp
No
strncmp
Yes




memcmpYes


strcasecmpNo
strncaseNo







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)