gcc 0长数组学习

首先,我们要知道,0长度的数组在ISO C和C++的规格说明书中是不允许的。这也就是为什么在VC++2012下编译你会得到一个警告:“warning C4200: 使用了非标准扩展 : 结构/联合中的零大小数组”。

那么为什么gcc可以通过而连一个警告都没有?那是因为gcc 为了预先支持C99的这种玩法,所以,让“零长度数组”这种玩法合法了。关于GCC对于这个事的文档在这里:“Arrays of Length Zero”,文档中给了一个例子(我改了一下,改成可以运行的了):

#include <stdlib.h>
#include <string.h>
 
struct line {
   int length;
   char contents[0]; // C99的玩法是:char contents[]; 没有指定数组长度
};
 
int main(){
    int this_length=10;
    struct line *thisline = (struct line *)
                     malloc (sizeof (struct line) + this_length);
    thisline->length = this_length;
    memset(thisline->contents, 'a', this_length);
    return 0;
}

 

上面这段代码的意思是:我想分配一个不定长的数组,于是我有一个结构体,其中有两个成员,一个是length,代表数组的长度,一个是contents,代码数组的内容。后面代码里的 this_length(长度是10)代表是我想分配的数据的长度。(这看上去是不是像一个C++的类?)这种玩法英文叫:Flexible Array,中文翻译叫:柔性数组。

我们来用gdb看一下:

(gdb) p thisline
$1 = (struct line *) 0x601010
 
(gdb) p *thisline
$2 = {length = 10, contents = 0x601010 "/n"}
 
(gdb) p thisline->contents
$3 = 0x601014 "aaaaaaaaaa"

 

我们可以看到:在输出*thisline时,我们发现其中的成员变量contents的地址居然和thisline是一样的(偏移量为0×0??!!)。但是当我们输出thisline->contents的时候,你又发现contents的地址是被offset了0×4了的,内容也变成了10个‘a’。(我觉得这是一个GDB的bug,VC++的调试器就能很好的显示)

我们继续,如果你sizeof(char[0])或是 sizeof(int[0]) 之类的零长度数组,你会发现sizeof返回了0,这就是说,零长度的数组是存在于结构体内的,但是不占结构体的size。你可以简单的理解为一个没有内容的占位标识,直到我们给结构体分配了内存,这个占位标识才变成了一个有长度的数组。

看到这里,你会说,为什么要这样搞啊,把contents声明成一个指针,然后为它再分配一下内存不行么?就像下面一样。

struct line {
   int length;
   char *contents;
};
 
int main(){
    int this_length=10;
    struct line *thisline = (struct line *)malloc (sizeof (struct line));
    thisline->contents = (char*) malloc( sizeof(char) * this_length );
    thisline->length = this_length;
    memset(thisline->contents, 'a', this_length);
    return 0;
}

 

这不一样清楚吗?而且也没什么怪异难懂的东西。是的,这也是普遍的编程方式,代码是很清晰,也让人很容易理解。即然这样,那为什么要搞一个零长度的数组?有毛意义?!

这个事情出来的原因是——我们想给一个结构体内的数据分配一个连续的内存!这样做的意义有两个好处:

第一个意义是,方便内存释放。如果我们的代码是在一个给别人用的函数中,你在里面做了二次内存分配,并把整个结构体返回给用户。用户调用free可以释放结构体,但是用户并不知道这个结构体内的成员也需要free,所以你不能指望用户来发现这个事。所以,如果我们把结构体的内存以及其成员要的内存一次性分配好了,并返回给用户一个结构体指针,用户做一次free就可以把所有的内存也给释放掉。(读到这里,你一定会觉得C++的封闭中的析构函数会让这事容易和干净很多)

第二个原因是,这样有利于访问速度。连续的内存有益于提高访问速度,也有益于减少内存碎片。(其实,我个人觉得也没多高了,反正你跑不了要用做偏移量的加法来寻址)

我们来看看是怎么个连续的,用gdb的x命令来查看:(我们知道,用struct line {}中的那个char contents[]不占用结构体的内存,所以,struct line就只有一个int成员,4个字节,而我们还要为contents[]分配10个字节长度,所以,一共是14个字节)

(gdb) x /14b thisline
0x601010:       10      0       0       0       97      97      97      97
0x601018:       97      97      97      97      97      97

 

从上面的内存布局我们可以看到,前4个字节是 int length,后10个字节就是char contents[]。

如果用指针的话,会变成这个样子:

(gdb) x /16b thisline
0x601010:       1       0       0       0       0       0       0       0
0x601018:       32      16      96      0       0       0       0       0
(gdb) x /10b this->contents
0x601020:       97      97      97      97      97      97      97      97
0x601028:       97      97

 

上面一共输出了四行内存,其中,

  • 第一行前四个字节是 int length,第一行的后四个字节是对齐。
  • 第二行是char* contents,64位系统指针8个长度,他的值是0×20 0×10 0×60 也就是0×601020。
  • 第三行和第四行是char* contents指向的内容。

从这里,我们看到,其中的差别——数组的原地就是内容,而指针的那里保存的是内容的地址

 

注:该文转自酷客,选取了其中的关于0长数组的部分,以前0长数组也见过,但是为什么要用呢,有什么好处呢,通过该文应该有一个了解!

Linux中10个有用的命令行补齐命令

本文转自GeekFan,感觉确实比较极客范啊,[TAB][TAB]补全都知道,但是你知道可以定制化补全吗?

——————————————————————————————————-

本文由 极客范 – 踏雁寻花 翻译自 Balakrishnan Mariyappan。欢迎加入极客翻译小组,同我们一道翻译与分享。转载请参见文章末尾处的要求。

在Linux系统中,输入一个命令,再按两次TAB键,就会列出所有以输入字符开头的可用命令。这并不新鲜,很可能你已经知道了这个。这个功能被称作命令补全。默认情况下,bash命令行可以自动补全文件或目录名称。不过,我们可以使bash命令行补全执行更多的操作,通过补全命令可以让它成就下一个辉煌。

这个教程说明了我们是怎样使用可编程的命令行补全功能(programmable completion)把自动补全的功能应用于选项或者命令行参数。

例如:在输入write 命令之后,如果你按两次TAB按键,自动补全功能会提供执行write操作的列表。

$write[TAB][TAB]

bala      raj
jason     randy
john      ritu
mayla     thomas
nisha     wwwdata
 
在下面的例子中,输入telnet命令将会显示可用了主机名:

$ telnet [TAB][TAB]
localhost dev-db fileserver

要让可编程命令补全功能在你的终端起作用 ,你只需要执行/etc/bash_completion即可,就像下面展示出来的操作:

# . /etc/bash_completion

你也可以取消/etc/bash.bashrc(对于Ubuntu Linux 13.04系统)下面的注释,这样,你就可以不需要执行上面的命令了,

enable bash completion in interactive shells
if ! shopt -oq posix; then
if [ -f /usr/share/bash-completion/bash_completion ]; then
. /usr/share/bash-completion/bash_completion
elif [ -f /etc/bash_completion ]; then
. /etc/bash_completion
fi
fi

如果你没有发现这些代码,也没有找到/etc/bash_completion文件,那么你只需要通过使用apt-get命令来安装bash_completion 包即可。

1、查看现有的bash补全命令

启用可编程的bash命令行补全功能,就可以定义一套bash补全命令。命令行补全可以用来定义bash补全命令。

来看一下现有的bash补全功能,使用完整的命令,如下:

complete -p | less

选项 -p 是可选择的。

2、Bash中标准补全的列表

Bash为linux用户默认提供了下面的标准补全命令。

  1. 变量名补全(Variablename completion)
  2. 用户名补全(Username completion)
  3. 主机名补全(Hostname completion)
  4. Path路径补全(Pathname completion)
  5. 文件名补全(Filename completion)

我们已经在更早的一篇文章bash standard completion 中讨论了这些。

3、为获取命令定义补全命令

使用-c参数定义一个补全命令来获得可使用的命令列表。在下面的例子中,为which命令定义了补全命令,

$ complete -c which

$ which [TAB][TAB]
Display all 2116 possibilities? (y or n)

就像上面看到的,如果按”y”,所有的命令都会显示出来。

4、为获得目录定义补全命令

使用参数d,定义一个只获得目录名称的补全命令,下面的例子中,定义了ls的补全命令

$ ls
countfiles.sh dir1/ dir2/ dir3/

$ complete -d ls

$ ls [TAB][TAB]
dir1/ dir2/ dir3/

就像上面看到的,连续按两次TAB,就可以看到目录名称。

5、为获得后台作业名称获得补全命令

通过使用complete命令,把获得job名称作为参数是允许的。参数j用来把job名称作为参数传到命令行,展示如下:

$ jobs
[1]- Stopped cat
[2]+ Stopped sed ‘p’

$ complete -j ./list_job_attrib.sh

$ ./list_job_attrib.sh [TAB][TAB]
cat sed

想要了解更多的后台任务,可以通过这些案例来了解下如何管理Linux 后台任务

6、使用前缀和后缀补全命令

补全命令可以通过被前缀(在后面添加)和后缀(添加在后面)来定义。在下面的例子中,前缀和后缀在list_job_attrib.sh中被定义。

$ jobs
[1]+ Stopped cat

$ complete -P ‘”&gt;’ -S ‘&lt;”‘ ./list_job_attrib.sh

$ ./list_job_attrib.sh [TAB][TAB]

$ ./list_job_attrib.sh “&gt;cat&lt;”

7、具有排除功能的文件名和目录补全

看看下面的脚本,输出output 目录下面的文件:

$ cd output/

$ ls
all_calls.txt incoming_calls.txt outgoing_calls.txt missed_calls.txt
parser_mod.tmp extract.o

在上面的例子中,如果你想要排除以.tmp和.o为后缀的文件,实现ls命令的自动补全功能,可以这样:

$ export FIGNORE=’.tmp:.o’

$ complete -f -d ls

$ cd output

$ ls [TAB][TAB]

FIGNORE 是一个shell变量,它包含了排除在自动补全队列中的文件的文件名的后缀。

8、通过IFS变量分割String字符串,得到被分割后的值。

单词表可以通过使用w参数被IFS 变量中定义的字符串分割成多个单词。最终每个单词都会被分开,被显示出来。

$ export IFS=” “

$ complete -W “bubble quick” ./sort_numbers.sh

$ ./sort_numbers.sh [TAB][TAB]
bubble quick

如上所述,被IFS分割之后,单词就会被扩展开,所以也可能有下面展示的这些变量。

$ echo $SORT_TYPE1
bubble

$ echo $SORT_TYPE2
quick

$ complete -W “$SORT_TYPE1 $SORT_TYPE2” ./sort_numbers.sh
$ ./sort_numbers.sh [TAB][TAB]
bubble quick

9、编写你自己的函数以实现自动补全功能

你可以声明一个函数来定义补全功能。使用 -F 参数,被传入到补全命令的函数名,可以执行并。例如,函数可以写成下面的样式。

_parser_options()
{
local curr_arg;

curr_arg=${COMP_WORDS[COMP_CWORD]}

COMPREPLY=( $(compgen -W ‘-i –incoming -o –outgoing -m –missed’ — $curr_arg ) );
}

在上面的函数中,

  1. COMPREPLY :存储在按下[TAB][TAB]之后打印信息的数组。
  2. COMP_WORDS :在命令行输入的单词数组
  3. COMP_CWORD :COMP_WORDS 数组的索引,可以访问命令行中不用位置的单词。
  4. compgen :使用-W参数,持有current_arg变量中尽可能完整的、分开的内容。

文件中parser_option 函数通过source执行如下:

$ source parser_option

这个函数链接到脚本解析器如下:

$ complete -F _parser_options ./parser.pl

$ ./parser.pl [TAB][TAB]
-i –incoming -o –outgoing -m –missed

就像上面所看到的,解析器的参数可以通过_parser_options函数生成。

注意:查看/etc/bash_completion文件,了解更多的可编程的命令行补全功能函数。

10、当第一规范没有进行匹配时,就需要执行第二规范

通过定义的补全规范,没有进行匹配,那么通过-o参数定义的completion 就会执行。

$ complete -F _count_files -o dirnames ./countfiles.sh

同上,通过使用_count_files 文件中定义的_count_files 函数定义的completion ,如果_count_files函数没有进行匹配,那么就会执行目录补全。

$ ls
countfiles.sh dir1/ dir2/ dir3/

$./countfiles.sh [TAB][TAB]
dir1 dir2 dir3