unbutun的专栏此人博客需要关注

来源:互联网 发布:如何考核软件开发 编辑:程序博客网 时间:2024/06/05 19:23
unbutun的专栏ftrace和perf 今天没去参加吃喝玩乐,跑去加班,不过心情还是愉悦的,因为有收获。真是利器啊。调试例子ftrace_demo.c/** ftrace_demo.c*/#include #include  #include  MODULE_LICENSE("GPL"); static int ftrace_demo_init(void){        trace_printk("Can not see this in trace unless loaded for the second time\n");        tracing_off();        return 0;} static void ftrace_demo_exit(void){        trace_printk("Module unloading\n");        tracing_off();} module_init(ftrace_demo_init);module_exit(ftrace_demo_exit);Makefileobj-m += ftrace_demo.o all:        make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modulesclean:        make -C /lib/modules/$(shell uname -r)/build M=$(PWD) cleanubuntu 10.10的内核里已经打开ftrace,看看/sys/kernel/debug/tracing/ 下的README,这就是使用方法。tracing mini-HOWTO: # mount -t debugfs nodev /sys/kernel/debug # cat /sys/kernel/debug/tracing/available_tracerswakeup preemptirqsoff preemptoff irqsoff function sched_switch nop # cat /sys/kernel/debug/tracing/current_tracernop# echo sched_switch > /sys/kernel/debug/tracing/current_tracer# cat /sys/kernel/debug/tracing/current_tracersched_switch# cat /sys/kernel/debug/tracing/trace_optionsnoprint-parent nosym-offset nosym-addr noverbose# echo print-parent > /sys/kernel/debug/tracing/trace_options# echo 1 > /sys/kernel/debug/tracing/tracing_enabled# cat /sys/kernel/debug/tracing/trace > /tmp/trace.txt# echo 0 > /sys/kernel/debug/tracing/tracing_enabled巧妙的应用了gcc 的-pg选项,重载mcount,实现tracing的功能。据yakui说,这个只适用于kernel space,于是有了[4],于是调试user space也方便了。之后是如何在要跟踪的代码中插入断点,参考[5]中的tracepoints.txt. 有几个commit可以参考。kernel 中的 99ee7fac189893c90145a22b86bbcfdc98f69a9c 和 9d34e5db07303c9609053e2e651aa6d1fc74e923。 第二个,还没看明白。折腾的功夫没到家啊。继续前面的学习,并补充。编译kernel,使之支持ftrace,需要打开如下选项CONFIG_FUNCTION_TRACERCONFIG_FUNCTION_GRAPH_TRACERCONFIG_STACK_TRACERCONFIG_DYNAMIC_FTRACE[7]中的代码,分析如下TRACE_EVENT(sched_switch,        TP_PROTO(struct rq *rq, struct task_struct *prev,                 struct task_struct *next),         TP_ARGS(rq, prev, next),         TP_STRUCT__entry(                __array(        char,   prev_comm,      TASK_COMM_LEN   )                __field(        pid_t,  prev_pid                        )                __field(        int,    prev_prio                       )                __field(        long,   prev_state                      )                __array(        char,   next_comm,      TASK_COMM_LEN   )                __field(        pid_t,  next_pid                        )                __field(        int,    next_prio                       )        ),         TP_fast_assign(                memcpy(__entry->next_comm, next->comm, TASK_COMM_LEN);                __entry->prev_pid       = prev->pid;                __entry->prev_prio      = prev->prio;                __entry->prev_state     = prev->state;                memcpy(__entry->prev_comm, prev->comm, TASK_COMM_LEN);                __entry->next_pid       = next->pid;                __entry->next_prio      = next->prio;        ),         TP_printk("prev_comm=%s prev_pid=%d prev_prio=%d prev_state=%s ==> next_comm=%s next_pid=%d next_prio=%d",                __entry->prev_comm, __entry->prev_pid, __entry->prev_prio,                __entry->prev_state ?                __print_flags(__entry->prev_state, "|",                        { 1, "S"} , { 2, "D" }, { 4, "T" }, { 8, "t" },                        { 16, "Z" }, { 32, "X" }, { 64, "x" },                        { 128, "W" }) : "R",                __entry->next_comm, __entry->next_pid, __entry->next_prio));#define TRACE_EVENT(name, proto, args, tstruct, assign, print) \        DECLARE_EVENT_CLASS(name,                              \                             PARAMS(proto),                    \                             PARAMS(args),                     \                             PARAMS(tstruct),                  \                             PARAMS(assign),                   \                             PARAMS(print));                   \        DEFINE_EVENT(name, name, PARAMS(proto), PARAMS(args));kernel里的宏,翻起来太累了,看了include/trace/ftrace.h,彻底晕菜。还是让gcc来帮忙吧。radeon驱动里的例子,非常适合借鉴  gcc -E radeon_trace_points.c  -I/work/linux-2.6/include -I/work/linux-2.6/include/drm -I/work/linux-2.6/arch/x86/include -I. -DCONFIG_TRACEPOINTS只看TRACE_EVENT(radeon_bo_create,            TP_PROTO(struct radeon_bo *bo),            TP_ARGS(bo),            TP_STRUCT__entry(                             __field(struct radeon_bo *, bo)                             __field(u32, pages)                             ),             TP_fast_assign(                           __entry->bo = bo;                           __entry->pages = bo->tbo.num_pages;                           ),            TP_printk("bo=%p, pages=%u", __entry->bo, __entry->pages));static const char __tpstrtab_radeon_bo_create[] __attribute__((section("__tracepoints_strings"))) = "radeon_bo_create";struct tracepoint __tracepoint_radeon_bo_create __attribute__((section("__tracepoints"))) = {        __tpstrtab_radeon_bo_create,        0,        ((void *)0),        ((void *)0),        ((void *)0) };static struct tracepoint * const __tracepoint_ptr_radeon_bo_create __used __attribute__((section("__tracepoints_ptrs"))) = __tracepoint_radeon_bo_create;;;1. ftrace简介http://www.ibm.com/developerworks/cn/linux/l-cn-ftrace/index.html2. 使用 ftrace 调试 Linux 内核http://linux.cn/home/space-2-do-blog-id-326.htmlhttp://linux.cn/home/space-2-do-blog-id-329.htmlhttp://linux.cn/home/space-2-do-blog-id-331.html3. Perf — Linux下的系统性能调优工具介绍http://blog.csdn.net/bluebeach/archive/2010/09/28/5912062.aspx4. trace: Add user-space event tracing/injectionhttp://marc.info/?l=linux-kernel&m=128999569714133&w=25.第一手资料在kernel目录中 Documentation/trace/*6. TRACE_EVENT()http://lwn.net/Articles/379903/7. sched_switch TRACE_EVENT definitionhttp://lwn.net/Articles/379903/http://lwn.net/Articles/381064/http://lwn.net/Articles/383362/作者:unbutun 发表于2011-7-11 20:44:21 原文链接阅读:3 评论:0 查看评论console,uart,tty的关联关系 /dev/console是系统控制台,或者说是物理控制台,而/dev/ttyn则是虚拟控制台,你可以传导诸如console=ttyS1的启动参数给内核,使得ttyS1(第二个串口控制台)成为系统控制台。如果没有传导这个参数给内核,内核就会自动寻找可用的设备作为系统控制台,首先是VGA显示卡,其次才是串口。/dev/console的初始早于虚拟控制台。启动信息都是打印到/dev/console。single模式登录,也是登录到/dev/console内核的printk信息,比如netfilter过滤了包的信息,也会打印到/dev/console,所以有人会问为何屏幕都是包过滤的信息。/proc/sys/kernel/printk,控制了printk的level。/dev/tty是当前进程连接的控制台,你可以echo something >/dev/tty 看看结果。/dev/tty0则是当前前台的视频控制台,你也可以echo something看看。/dev/tty1-n则是虚拟控制台。晕。说错纠正啊。  tty是一类char设备的通称,它们有相同的特性,比如对^C的处理,驱动使用tty_register_driver注册一个tty。/dev/console是一个虚拟的tty,它映射到真正的tty上,console有多种含义,这里特指printk输出的设备,驱动使用register_console注册一个console。console和tty有很大区别:console是个只输出的设备,功能很简单,只能在内核中访问;tty是char设备,可以被用户程序访问。实际的驱动比如串口对一个物理设备会注册两次,一个是tty,一个是console,并通过在console的结构中记录tty的主次设备号建立了联系。在内核中,tty和console都可以注册多个。当内核命令行上指定console=ttyS0之类的参数时,首先确定了printk实际使用那个 console作为输出,其次由于console和tty之间的对应关系,打开/dev/console时,就会映射到相应的tty上。用一句话说: /dev/console将映射到默认console对应的tty上。 作者:unbutun 发表于2011-7-10 18:59:31 原文链接阅读:3 评论:0 查看评论郁闷的串口中断 nobody cared  这几天碰到一个很奇怪的问题, kernel 启动的时候,会碰到 抱怨 irq 19: nobody cared. 这个中断是串口中断. 而且我确认串口是可以work的. 因为在 early console->console 已经成功了. 经过一番搜索, 发现把kernel_init 函数中的 /* Open the /dev/console on the rootfs, this should never fail */ if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0) printk(KERN_WARNING "Warning: unable to open an initial console.\n"); 注释掉就可以ok. 看来问题是 在 sys_open((const char __user *) "/dev/console" 中. 我的问题是: 到这里的时候, rootfs 还没有被 mount上(我的rootfs是放在USB上面的),我加了一些log,发现在 打开/dev/console后才会 去mount USB的rootfs. 那么这个时候去open /dev/console, 一定会失败了. 但是 注释说 , this should never fail. 是不是在mount rootfs之前,kernel 会创建这个设备? 在那里创建的? NET: Registered protocol family 17 NET: Registered protocol family 15 Registering platform devices irq 19: nobody cared (try booting with the "irqpoll" option) Call Trace: [<ffffffff80211150>] dump_stack+0x8/0x34 [<ffffffff80299ec4>] __report_bad_irq+0x44/0xe8 [<ffffffff8029a0f0>] note_interrupt+0x188/0x250 [<ffffffff8029aa7c>] handle_percpu_irq+0xb4/0xe0 [<ffffffff80216fd4>] do_IRQ+0x2c/0x40 [<ffffffff80200400>] ret_from_irq+0x0/0x4 [<ffffffff80244430>] __do_softirq+0x98/0x270 [<ffffffff802446a8>] do_softirq+0xa0/0xc8 [<ffffffff80244784>] irq_exit+0xb4/0xd8 [<ffffffff80200400>] ret_from_irq+0x0/0x4 [<ffffffff80299028>] __setup_irq+0x1f8/0x440 [<ffffffff80299394>] request_threaded_irq+0x124/0x188 [<ffffffff804a0fac>] serial8250_startup+0xb6c/0xca8 [<ffffffff804982ec>] uart_startup+0x74/0x290 [<ffffffff8049b0a0>] uart_open+0x190/0x648 [<ffffffff8046f388>] tty_open+0x288/0x850 [<ffffffff802e7f14>] chrdev_open+0x1e4/0x370 [<ffffffff802e02e0>] __dentry_open+0xf0/0x348 [<ffffffff802f5578>] do_last+0x720/0xa10 [<ffffffff802f5a58>] do_filp_open+0x1f0/0x670 [<ffffffff802e1d4c>] do_sys_open+0x9c/0x1d8 [<ffffffff80854228>] kernel_init+0x130/0x1b4 [<ffffffff802178f8>] kernel_thread_helper+0x10/0x18 handlers: [<ffffffff8049f918>] (serial8250_interrupt+0x0/0x188) Disabling IRQ #19 irq 19: nobody cared (try booting with the "irqpoll" option) Call Trace: [<ffffffff80211150>] dump_stack+0x8/0x34 [<ffffffff80299ec4>] __report_bad_irq+0x44/0xe8 [<ffffffff8029a0f0>] note_interrupt+0x188/0x250 [<ffffffff8029aa7c>] handle_percpu_irq+0xb4/0xe0 [<ffffffff80216fd4>] do_IRQ+0x2c/0x40 [<ffffffff80200400>] ret_from_irq+0x0/0x4 [<ffffffff80244430>] __do_softirq+0x98/0x270 [<ffffffff802446a8>] do_softirq+0xa0/0xc8 [<ffffffff80244784>] irq_exit+0xb4/0xd8 [<ffffffff80200400>] ret_from_irq+0x0/0x4 [<ffffffff80299028>] __setup_irq+0x1f8/0x440 [<ffffffff80299394>] request_threaded_irq+0x124/0x188 [<ffffffff804a0fac>] serial8250_startup+0xb6c/0xca8 [<ffffffff804982ec>] uart_startup+0x74/0x290 [<ffffffff8049b0a0>] uart_open+0x190/0x648 [<ffffffff8046f388>] tty_open+0x288/0x850 [<ffffffff802e7f14>] chrdev_open+0x1e4/0x370 [<ffffffff802e02e0>] __dentry_open+0xf0/0x348 [<ffffffff802f5578>] do_last+0x720/0xa10 [<ffffffff802f5a58>] do_filp_open+0x1f0/0x670 [<ffffffff802e1d4c>] do_sys_open+0x9c/0x1d8 [<ffffffff80854228>] kernel_init+0x130/0x1b4 [<ffffffff802178f8>] kernel_thread_helper+0x10/0x18  grep了一把, 发现是在 drivers/char/tty_io.c 中 创建 /dev/console. 那为什么会open的时候有问题呢? 继续debug 3128 static int __init tty_init(void) 3129 { 3130 cdev_init(&tty_cdev, &tty_fops); 3131 if (cdev_add(&tty_cdev, MKDEV(TTYAUX_MAJOR, 0), 1) || 3132 register_chrdev_region(MKDEV(TTYAUX_MAJOR, 0), 1, "/dev/tty") < 0) 3133 panic("Couldn't register /dev/tty driver\n"); 3134 device_create(tty_class, NULL, MKDEV(TTYAUX_MAJOR, 0), NULL, 3135 "tty"); 3136 3137 cdev_init(&console_cdev, &console_fops); 3138 if (cdev_add(&console_cdev, MKDEV(TTYAUX_MAJOR, 1), 1) || 3139 register_chrdev_region(MKDEV(TTYAUX_MAJOR, 1), 1, "/dev/console") < 0) 3140 panic("Couldn't register /dev/console driver\n"); 3141 device_create(tty_class, NULL, MKDEV(TTYAUX_MAJOR, 1), NULL, 3142 "console"); http://lists.openwall.net/linux-kernel/2010/03/03/197 这段代码看来是 3/3 kernel中刚改过的. 原来 打开 /dev/console 是在 init_post 中做的. 感觉你遇到的问题,跟我的类似阿。 我也不知道,什么时候,谁在/dev下创建的设备。 其实谁创建的,我知道,是udev线程创建的。关键谁让udev创建的? LVM,块设备,设计到很多东西。 这个问题 今天解决了。 现在看一下表面现象: 表面现象是 irq 19没有人 care。 那么我们的第一反应是:irq 19的中断处理函数没有注册。 但是从kernel给出的信息又说明它找到了handlers: [<ffffffff8049f918>] (serial8250_interrupt+0x0/0x188) 因此,没有注册irq 19的中断处理程序的推理不成立。 那么继续想一下,kernel是如何判断这个irq没有人care呢? 具体的代码在note_interrupt 里面。kernel 会记录某一个irq没有被处理的次数。如果在某一段时间里面这个irq没有被处理达到一个大的阀值,kernel 就认为这个irq没有人care。 因此,实际上,这个错误信息是: irq 19 在很长的时间里面没有人处理。 那再回到具体的平台中。平台的IRQ 19是UART,这个中断是连接到MIPS CPU的IP3。熟悉MIPS的同学都知道,中断由 MIPS CPU的中断处理程序再根据具体的中断引脚 调用具体的doIRQ。 比如,在MIPS 中断处理中,如果发现IP3被 set,就会调用 IRQ(19)。当然不同的平台中断的路由不一样,但是大体的思路是一样的。 跟踪代码,发现代码进入了MIPS的中断处理函数,也确实判断了IP3被set,然后也调用了 doIRQ(19)。但是,为什么kernel 认为这个中断没有人处理呢? 实际上,代码也进了 serial8250_interrupt。 这个函数会读 串口的IIR寄存器,这个寄存器反应了串口的中断状态,读出来的值是 0x1,也就是 NO INTERRUPT PENDING. 因此,会认为当前没有串口发生,跳出中断处理函数。 从而导致kernel 认为该中断没有处理。 那么就有个问题。 刚才说 串口中断是接到CPU 的IP3上面。先是cpu认为IP3被set,然后才调用串口中断处理程序。 如果没有串口中断,为什么CPU的IP3会被set,从而MIPS CPU认为当前有中断发生呢?这个是核心所在。 查了一些设定发现,对于龙芯的北桥设定有问题,导致 MIPS CPU的 IP2和IP3一直被set,也就是一直认为有中断发生。 由于在 sys_open((const char __user *) "/dev/console", O_RDWR, 0) 前,串口并没有使用中断因此没有问题。 但是一旦 串口采用中断了以后,立刻问题就出现了。 重新设置了龙芯的北桥后,问题解决。 两个结论: (1) 要抓住问题的核心。有点时候kernel的错误信息有一些误导。 (2) 串口只有在进入user land前才使用中断。 结贴。 作者:unbutun 发表于2011-7-10 12:46:06 原文链接阅读:6 评论:0 查看评论ELF Bin 文件查看 ELF Bin 文件区别 嵌入式开发的时候,我们的编译一个*.S文件,并最终生成bin文件,编译命令大致如下:CC=arm-softfloat-linux-gnu-gccLD=arm-softfloat-linux-gnu-ldOBJCOPY=arm-softfloat-linux-gnu-objcopy$(CC) -g $(CFLAG) -c boot.S     #先将boot.S文件生成boot.o$(LD) -g -Bstatic -T$(LDFILE) -Ttext 0x12345600 boot.o --start-group -Map boot.map -o boot.elf    #再将boot.o生成boot.elf, boot.elf通常就是可执行文件,类似于gcc -o test test.c 中的test文件,在Linux Shell下输入./test就可以执行。$(OBJCOPY) -O binary boot.elf boot.bin    #接着将boot.elf->boot.bin,这样可以缩小代码尺寸。运行arm-softfloat-linux-gnu-objdump -h boot.elf 可以查看该文件的信息,但是如果变成arm-softfloat-linux-gnu-objdump -h boot.bin会提示错误,为了看boot.bin文件信息,输入:arm-softfloat-linux-gnu-objdump -h -b binary -m arm boot.bin就可以了。哈哈我也是现学了一招,其中参数-h可以被替换成为-D, -S, -s等等,请用arm-softfloat-linux-gnu-objdump --help查看器中表示的意义。说说ELF Bin 文件区别:我们有了Linux OS,为了运行可执行文件,他们是遵循ELF格式的,通常gcc -o test test.c,生成的test文件就是ELF格式的,这样就可以运行了。arm-softfloat-linux-gnu-objcopy命令将去掉ELF格式的东西,仅仅保留最纯的汇编(不知道如何解释),在Embedded中,如果上电开始运行,没有OS系统,如果将ELF格式的文件烧写进去,包含一些ELF格式的东西,arm运行碰到这些指令,就会导致失败,如果用arm-softfloat-linux-gnu-objcopy生成纯粹的汇编,程序就可以一步一步运行。 本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/yudingding6197/archive/2009/02/06/3866140.aspx作者:unbutun 发表于2011-7-10 12:17:48 原文链接阅读:2 评论:0 查看评论vmlinux,vmlinuz,zImage,bzImage几个内核文件有啥区别,哪位聊聊,谢谢! 我在cirrus网站下载的内核及补丁文件,编译后看到三个内核文件,分别如下: -rwxrwxr-x 1 wmb wmb 2066336 3月 9 14:22 vmlinux -rwxrwxr-x 1 wmb wmb 840572 3月 9 14:22 arch/arm/boot/compressed/vmlinux -rwxrwxr-x 1 wmbwmb 800208 3月 9 14:22 arch/arm/boot/zImage 不知道有什么不同,而且前两个文件名称一样,大小却不同.  vmlinux 是ELF文件,即编译出来的最原始的文件。 vmlinuz应该是vmlinux经过objcopy处理的文件,应该是个bin文件(不太确定了) zImage是vmlinuz经过gzip压缩后的文件 bzImage是vmlinuz经过bzip压缩后的文件 上面我说的有误,不好意思。 vmlinuz应该是由ELF文件vmlinux经过OBJCOPY后,并经过压缩后的文件。  作者:unbutun 发表于2011-7-10 12:16:54 原文链接阅读:2 评论:0 查看评论A few examples of how to use :g in vimVimTip 227: Power of :ghttp://vim.sourceforge.net/tip_view.php?tip_id=:g is something very old and which is very powerful. I just wanted to illustrate the use of itwith some examples. Hope, it will be useful for someone.Brief explanation for ":g"-------------------------Syntax is:    :[range]:g/<pattern>/[cmd]You can think the working as, for the range (default whole file), executethe colon command(ex) "cmd" for the lines matching <pattern>. Also, for alllines that matched the pattern, "." is set to that particular line (forcertain commands if line is not specified "." (current line) is assumed).Some examples-------------Display context (5 lines) for all occurences of a pattern    :g/<pattern>/z#.5    :g/<pattern>/z#.5|echo "=========="    << same as first, but with some beautification >>Delete all lines matching a pattern    :g/<pattern>/dDelete all blank lines (just an example for above)    :g/^s*$/dDouble space the file    :g/^/pu ="n"    :g/^/pu _    << the above one also works >>Copy all lines matching a pattern to end of file    :g/<pattern>/t$Yank all lines matching a pattern to register 'a'    0"ay0:g/<pattern>/y AIncrement the number items from current line to end-of-document by one    :.,$g/^d/exe "normal! <c-a>"Comment (C) lines containing "DEBUG" statements    g/^s*DEBUG/exe "norm! I/* <Esc>A */<Esc>"A Reverse lookup for records(eg: An address book, with Name on start-of-line and fields after a space)    :g/<patern>?^w?p               "if only name is interested    :g/<patern>/ka|?^w?p|'ap       "if name and the lookup-line is interested    :g/<patern>/?^w?|+,/^[^ ]/-1p  "if entire record is interestedReverse a file (just to show the power of 'g')    :g/^/m0Foot note 1: use :v to negate the search patternFoot note 2: Some explanation of commonly used commands with :g :2,8co15 => Copy lines 2 through 8 after line 15 :4,15t$  => Copy linesa 4 through 15 towards end of document (t == co)    :-t$  => Copy previous line to end of document     :m0  => Move current line to the top of the document:.,+3m$-1 => Move current line through cur-line+3 to the last but one line             of the documentFoot note 3: Commands used with :g are ex commands, so a help search should             be,                :help :<help-topic>                eg. :help :kg in vim    作者:unbutun 发表于2011-7-9 10:20:39 原文链接阅读:1 评论:0 查看评论Bash Brace Expansion Tutorial: 6 Examples of Expanding Expressions within Braces One of the operation of the shell when it analyzes the input is Shell expansion. Bash provides different types of expansion. In this article let us review an important expansion — “Brace expansion”.This article is part of our on-going Bash Tutorial series.Brace ExpansionBrace expansion is used to generate arbitrary strings. Brace expansion allows you to create multiple modified command line arguments out of a single argument. The specified strings are used to generate all possible combination with the optional surrounding preambles and postscripts. The preamble is prefixed to each string contained within the braces, and the postscript is then appended to each resulting string, expanding left to right.$ echo last{mce,boot,xorg}.loglastmce.log lastboot.log lastxorg.logwhere last is Preamble and .log is the postscriptThe above echo statement avoids you to specifying the three log files separately. If you want to view the content of the last boot log, mce log and xorg log you can use the brace expansion as shown in the above echo statement.1. Example for Backup using brace expansion$ cat bkup.shset -x # expand the commandsda=`date +%F`cp $da.log{,.bak}$ ./bkup.sh++ date +%F+ da=2010-05-28+ cp 2010-05-28.log 2010-05-28.log.bakIn the above backup script, it copies the current date log file with the extension .bak. The first element is empty in the braces, so first element will have only preamble.2. Example for Restore using brace expansion$ cat restore.shset -x # expand the commandsda=`date +%F`cp $da.log{.bak,}$ ./restore.sh++ date +%F+ da=2010-05-28+ cp 2010-05-28.log.bak 2010-05-28.logIn the restore script, the first element in the parameter is .bak where as second element is empty.Also, refer to our earlier article on bash shell functions for additional reading.3. Example for Brace Expansion without preamble and postscriptIf there is no preamble and postscript, it just expands the elements given in the braces.$ cat expand.shecho {oct,hex,dec,bin}$ ./expand.shoct hex dec binWithout the optional preamble and postscript strings, the result is just a space separated list of the given stringsBrace expansion for RangesBrace expansion expands the sequences also. The sequences can be of integers or characters.4. Example for Integer and character sequences$ cat sequence.shcat /var/log/messages.{1..3}echo {a..f}{1..9}.txt$ ./sequence.shMay  9 01:18:29 x3 ntpd[2413]: time reset -0.132703 sMay  9 01:22:38 x3 ntpd[2413]: synchronized to LOCAL(0), stratum 10May  9 01:23:44 x3 ntpd[2413]: synchronized toMay  9 01:47:48 x3 dhclient: DHCPREQUEST on eth0May  9 01:47:48 x3 dhclient: DHCPACK from 23.42.38.201....a1.txt a2.txt a3.txt a4.txt b1.txt b2.txt b3.txt b4.txt c1.txt c2.txt c3.txt c4.txtThe first cat command, expands messages.1,messages.2 and messages.3 and displays the content. and in the next echo statement character and integer sequences are combined and used.Sequences with increment valueIn kshell brace expansion, you can use increment value, to generate the sequences.Syntax:<start>..<end>..<incr>incr is numeric. You can use a negative integer, but the correct sign is deduced from the order of start and end.5. Example for using Increment in sequences$ ksh$ echo /var/log/messages.{1..7..2}/var/log/messages.1 /var/log/messages.3 /var/log/messages.5 /var/log/messages.7$Using this you could see the alternate days logfiles.Pitfall in Brace expansionBrace expansion does not expand bash variables, because the brace expansion is the very first step of the shell expansion, variable will be expanded later.6. Example for Variables in expansionIf you see the output of the following two for statement, you could identify the above pitfall.$ cat var_seq.sh# Print 1 to 4 using sequences.for i in {1..4}do        echo $idonestart=1end=4# Print 1 to 4 using through variablesecho "Sequences expressed using variables"for i in {$start..$end}do        echo $idone$ ./var_seq.sh1234Sequences expressed using variables{1..4}作者:unbutun 发表于2011-7-9 8:38:12 原文链接阅读:1 评论:0 查看评论使用VIM开发软件项目 - (11) 剑不离手:quickfix 本节所用命令的帮助入口::help quickfix:help :make:help 'makeprg':help 'errorformat':help 'switchbuf':help location-list:help grep:help :vimgrep:help :grep:help starstar-wildcard以前读武侠小说,看到武林高手们都是从来剑不离手的。使用VIM开发软件项目,你也可以做到这一点,:-)VIM由一个程序员开发,而且为更多的程序所使用,所以在VIM中加强了对软件开发的支持,quickfix模式的引入就是一个例子。所谓quickfix模式,它和Normal模式、Insert模式没什么关系,它只是一种加速你开发的工作方式。Quickfix模式的主要思想是保存一个位置列表,然后提供一系列命令,实现在这个位置列表中跳转。位置列表的产生可以从编译器的编译输出信息中获得,也可以由grep命令的输出信息中获得,我们上篇文章所介绍的cscope命令,也可以产生位置列表信息(:help 'cscopequickfix')。[编译]通常,我们在开发过程中,经常要写代码,编译,修改编译错误,这个过程会数十遍上百遍的重复。如果你是根据编译器输出的错误信息,打开出错的文件,找到出错的行,然后再开始修改,那效率未免太低下了。利用VIM的quickfix模式,可以大大加快这一过程,你可以在VIM启动编译,然后VIM会根据编译器输出的错误信息,自动跳到第一个出错的地方,让你进行修改;修改完后,使用一个快捷键,跳到下一个错误处,再进行修改,方便的很。为了做到这一点,你首先要定义编译时所使用的程序,对大多数使用Makefile的项目来说,VIM的缺省设置“make”已经可以满足要求了。如果你的项目需要用一个特殊的程序进行编译,就需要修改'makeprg'选项的值。大家在学编程时大概都见到过"hello world"程序,我们就以这个简单的例子为例,讲一下quickfix模式的用法。该程序的内容如下,里面包含了三个小小的错误:/* hello world demo */#include <stdio.h"int main(int argc, char **argv){    int i;    print("hello world\n");    return 0;}我们可以为这个程序写个小小的Makefile文件,不过为了演示'makeprg'的设置方法,我们并不用Makefile,而直接设置'makeprg'选项,如下::set makeprg=gcc\ -Wall\ -ohello\ hello.c上面的命令会把hello.c编译为名hello的可执行文件,并打开了所有的Warnning。如果编译命令中有空格,需要使用'\'对其进行转义,上面的例子就是这种情况。我们设置好'makeprg'选项后,输入下面的命令就可以编译了::make在使用“:make”时,VIM会自动调用'makeprg'选项定义的命令进行编译,并把编译输出重定向到一个临时文件中,当编译出现错误时,VIM会从上述临时文件中读出错误信息,根据这些信息形成quickfix列表,并跳转到第一个错误出现的地方。对于我们上面的程序来说,光标会停在第三行,也就是第一个出错的位置,VIM同时会提示出错信息。如果你没看清出错信息,可以输入“:cc”命令,VIM会更次显示此信息,或者干脆使用“:cw”命令,打开一个quickfix窗口,把所有的出错信息显示出来,见下图:现在我们知道错在哪儿了,修正一下,然后使用“:cn”命令(或者在Quickfix List对应行上输入回车)跳到下一个出错的地方,以此类推,直到修正全部错误。好了,千辛万苦,我们的hello world终于工作了。乍一看这个例子,似乎Quickfix并没有提高什么效率,但如果你的错误出现在多个不同目录的文件里,它可以帮你省很多时间,使你可以集中精力在修正bug上。VIM可以同时记住最新的10个错误列表,也就是说你最近10次使用“:make”命令编译所遇到的错误都保存着,可以使用“:colder”和“:cnewer”命令,回到旧的错误列表,或者到更新的错误列表。在quickfix模式里经常用到的命令有::cc                显示详细错误信息 ( :help :cc ):cp                跳到上一个错误 ( :help :cp ):cn                跳到下一个错误 ( :help :cn ):cl                列出所有错误 ( :help :cl ):cw                如果有错误列表,则打开quickfix窗口 ( :help :cw ):col               到前一个旧的错误列表 ( :help :col ):cnew              到后一个较新的错误列表 ( :help :cnew )更多的命令,以及这些命令更详细的解释,请参见手册。对于经常用到的命令,最好提供更方便的使用方法,在我的vimrc中的定义:autocmd FileType c,cpp  map <buffer> <leader><space> :w<cr>:make<cr>nmap <leader>cn :cn<cr>nmap <leader>cp :cp<cr>nmap <leader>cw :cw 10<cr>现在使用“,<space>”就可以编译,使用“,cp”和“,cn”跳到上一个和下一个错误,使用“,cw”来打开一个quickfix窗口。这下顺手多了!如果你希望跳转到出错的文件时,使用一个分隔的窗口打开,请参阅'switchbuf'选项的值。在VIM7中,每个窗口都可以拥有自己的位置列表,这样,你就能够同时打开多个位置列表了,而quickfix列表在整个VIM中只有一个。你可以使用位置列表来显示编译错误信息,具体命令参阅手册:“:help location-list”以及“:help :lmake”作者:unbutun 发表于2011-7-7 20:01:15 原文链接阅读:2 评论:0 查看评论VIM下,在文件及目录中查找字符串的方法 (vimgrep) 以前用editplus的时候,有一个在文件中查找的功能,可以在所有打开的文件中查找字符串,也可以在某一个目录及它的子目录中查找.那么在VIM中是否也有相关的功能呢?答案当然是肯定的.VIM中有个类似grep的命令,叫做vimgrep,语法如下::vimgrep /{pattern}/[g][j] {file} ...    简单来讲,就是在路径和文件命符合{file}的所有文件中,查找符合{pattern}的字符串.(查找的结果可以用:copen命令打开quickfix列表查看).    没有参数g的话,则行只查找一次关键字.反之会查找所有的关键字.    没有参数j的话,查找后,VIM会跳转至第一个关键字所在的文件.反之,只更新结果列表(quickfix).    洒家再给几个例子,比方:vimgrep /the menu/ *.php表示在当前目录下的扩展名为php的所有文件中,查找字符串"the menu".:vimgrep /the menu/ ./includes/*.*表示在当前目录中的"includes"目录里的所有文件中,查找字符串"the menu".如果要在当前目录及其子目录中查找怎么办呢?也好办:vimgrep /the menu/ **/*.*用这句就可以了.    查找时{pattern}可用正则表达式,使用起来和'/'命令是一样的,就不多说了.    查找的结果可以用":copen"命令查看,在列表里,将光标移动至相应的位置,按回车就打开对应的文件了.    注:        :copen    打开quickfix        :cclose    关闭quickfix        :cc    是在转到当前查找到的位置        :cn    转到下一个位置        :cp    转到前一个位置    当然,用grep同样可以达到这个效果,不过用vimgrep的好处就是与系统无关,能适用于所有系统的VIM,而且能自动识别文件编码和换行.嘿嘿,VIM就是你用的越多就越能感觉到它的强大了./转载请注明出处与链接,谢谢! http://www.nerdlinux.com/post/43作者:unbutun 发表于2011-7-7 19:58:20 原文链接阅读:2 评论:0 查看评论Makefile 简明手册一个完整的 Makefile 通常由 "显式规则"、"隐式规则"、"变量定义"、"指示符"、"注释" 五部分组成。显式规则: 描述了在何种情况下如何更新一个或多个目标文件。隐式规则: make 默认创建目标文件的规则。(可重写)变量定义: 类似 shell 变量或 C 宏,用一个简短名称代表一段文本。指示符: 包括包含(include)、条件执行、宏定义(多行变量)等内容。注释: 字符 "#" 后的内容被当作注释。(1) make 会在工作目录按照 "GNUmakefile、makefile、Makefile(推荐)" 顺序查找并执行,或使用 "-f" 参数显式指定文件名。(2) 如果不在 make 命令行显式指定目标规则名,则默认使用第一个有效规则。(3) Makefile 中 "{1}quot;、"#" 有特殊含义,可以进行转义 "/#"、"$"。(4) 可以使用 "/" 换行(注释行也可以使用),但其后不能有空格,新行同样必须以 Tab 开头和缩进。注意: 本文中提到的目标文件通常是 ".o",类似的还有源文件(.c)、头文件(.h) 等。1. 规则规则组成方式:target...: prerequisites...    command    ...target: 目标。prerequisites: 依赖列表。一个或多个文件名列表(空格分隔,通常是 ".o, .c, .h" 等,可以使用通配符)。command: 命令行。shell 命令或程序,且必须以 Tab 开头(最容易犯的错误)。没有命令行的规则只能指示 "依赖关系",没有依赖项的规则指示 "如何" 构建目标,而非 "何时" 构建。目标的依赖列表可以通过 GCC -MM 参数获得。规则处理方式:(1) 目标文件不存在,使用其规则(显式或隐式规则)创建。(2) 目标文件存在,但如果任何一个依赖文件比目标文件修改时间 "新",则重新创建目标文件。(3) 目标文件存在,且比所有依赖文件 "新",则什么都不做。1.1 隐式规则当我们不编写显式规则时,隐式规则就会生效。当然我们可以修改隐式规则的命令。%.o: %.c    $(CC) $(CFLAGS) -o $@ -c {1}lt;未定义规则或者不包含命令的规则都会使用隐式规则。# 隐式规则%.o: %.c    @echo {1}lt;    @echo $^    $(CC) $(CFLAGS) -o $@ -c {1}lt;all: test.o main.o    $(CC) $(CFLAGS) $(LDFLAGS) -o $(OUT) $^main.o: test.o test.h输出:$ make./lib/test.c./lib/test.cgcc -Wall -g -std=c99 -I./lib -I./src -o test.o -c ./lib/test.c./src/main.c./src/main.c test.o ./lib/test.hgcc -Wall -g -std=c99 -I./lib -I./src -o main.o -c ./src/main.cgcc -Wall -g -std=c99 -I./lib -I./src -lpthread  -o test test.o main.o"test.o" 规则不存在,直接使用了隐式规则。"main.o" 没有命令,使用隐式规则的同时,还会合并依赖列表。1.2 模式规则在隐式规则前添加特定的目标,就形成了模式规则。test.o main.o: %.o: %.c    $(CC) $(CFLAGS) -o $@ -c {1}lt;1.3 搜索路径在实际项目中我们通常将源码文件分散在多个目录中,将这些路径写入 Makefile 会很麻烦,此时可以考虑用 VPATH 变量指定搜索路径。all: lib/test.o src/main.o    $(CC) $(CFLAGS) $(LDFLAGS) -o $(OUT) $^改写成 VPATH 方式后,要调整项目目录就简单多了。# 依赖目标搜索路径VPATH = ./src:./lib# 隐式规则%.o:%.c    -@echo "source file: {1}lt;"    $(CC) $(CFLAGS) -o $@ -c {1}lt;all:test.o main.o    $(CC) $(CFLAGS) $(LDFLAGS) -o $(OUT) $^执行:$ makesource file: ./lib/test.cgcc -Wall -g -std=c99 -I./lib -I./src -o test.o -c ./lib/test.csource file: ./src/main.cgcc -Wall -g -std=c99 -I./lib -I./src -o main.o -c ./src/main.cgcc -Wall -g -std=c99 -I./lib -I./src -lpthread  -o test test.o main.o还可使用 make 关键字 vpath。比 VPATH 变量更灵活,甚至可以单独为某个文件定义路径。vpath %.c ./src:./lib # 定义匹配模式(%匹配任意个字符)和搜索路径。vpath %.c # 取消该模式vpath # 取消所有模式相同的匹配模式可以定义多次,make 会按照定义顺序搜索这多个定义的路径。vpath %.c ./srcvpath %.c ./libvpath %.h ./lib1.4 伪目标当我们为了执行命令而非创建目标文件时,就会使用伪目标了,比如 clean。伪目标总是被执行。clean:    -rm *.o    .PHONY: clean使用 "-" 前缀可以忽略命令错误,".PHONY" 的作用是避免和当前目录下的文件名冲突(可能引发隐式规则)。2. 命令每条命令都在一个独立 shell 环境中执行,如希望在同一 shell 执行,可以用 ";" 将命令写在一行。test:    cd test; cp test test.bak提示: 可以用 "/" 换行,如此更美观一些。默认情况下,多行命令会顺序执行。但如果命令出错,默认会终止后续执行。可以添加 "-" 前缀来忽略命令错误。另外还可以添加 "@" 来避免显示命令行本身。all: test.o main.o    @echo "build ..."    @$(CC) $(CFLAGS) $(LDFLAGS) -o $(OUT) $^执行其他规则:all: test.o main.o    $(MAKE) info    @$(CC) $(CFLAGS) $(LDFLAGS) -o $(OUT) $^info:    @echo "build..."3. 变量Makefile 支持类似 shell 的变量功能,相当于 C 宏,本质上就是文本替换。变量名区分大小写。变量名建议使用字母、数字和下划线组成。引用方式 "$(var)" 或 "${var}"。引用未定义变量时,输出空。3.1 变量定义首先注意的是 "=" 和 ":=" 的区别。"=": 递归展开变量,仅在目标展开时才会替换,也就是说它可以引用在后面定义的变量。":=": 直接展开变量,在定义时就直接展开,它无法后置引用。A = "a: $(C)"B := "b: $(C"C = "haha..."all:    @echo $A    @echo $B输出:$ makea: haha...b:由于 B 定义时 C 尚未定义,所以直接展开的结果就是空。修改一下,再看。C = "none..."A = "a: $(C)"B := "b: $(C)"C = "haha..."all:    @echo $A    @echo $B输出:$ makea: haha...b: none...可见 A 和 B 的展开时机的区别。除了使用 "="、":=" 外,还可以用 "define ... endef" 定义多行变量(宏,递归展开,只需在调用时添加 "@" 即可)。define help    echo ""    echo "  make release : Build release version."    echo "  make clean   : Clean templ files."    echo ""endefdebug:    @echo "Build debug version..."    @$(help)    @$(MAKE) $(OUT) DEBUG=1release:    @echo "Build release version..."    @$(help)    @$(MAKE) clean $(OUT)3.2 操作符"?=" 表示变量为空或未定义时才进行赋值操作。A = "a"A ?= "A"B ?= "B"all:    @echo $A    @echo $B输出:$ makeaB"+=" 追加变量值。注意变量展开时机。A = "$B"A += "..."B = "haha"all:    @echo $A输出:$ makehaha ...3.3 替换引用使用 "$(VAR:A=B)" 可以将变量 VAR 中所有以 "A" 结尾的单词替换成以 "B" 结尾。A = "a.o b.o c.o"all:    @echo $(A:o=c)输出:$ makea.c b.c c.o3.4 命令行变量命令行变量会替换 Makefile 中定义的变量值,除非使用 override。A = "aaa"override B = "bbb"C += "ccc"override D += "ddd"all:     @echo $A    @echo $B    @echo $C    @echo $D执行:$ make A="111" B="222" C="333" D="444"111bbb333444 ddd我们注意到追加方式在使用 override 后才和命令行变量合并。3.5 目标变量仅在某个特定目标中生效,相当于局部变量。test1: A = "abc"test1:    @echo "test1" $Atest2:    @echo "test2" $A输出:$ make test1 test2test1 abctest2还可以定义模式变量。test%: A = "abc"test1:    @echo "test1" $Atest2:    @echo "test2" $A输出:$ make test1 test2test1 abctest2 abc3.5 自动化变量$?: 比目标新的依赖项。$@: 目标名称。{1}lt;: 第一个依赖项名称(搜索后路径)。$^: 所有依赖项(搜索后路径,排除重复项)。3.6 通配符在变量定义中使用通配符则需要借助 wildcard。FILES = $(wildcard *.o)all:    @echo $(FILES)3.7 环境变量和 shell 一样,可以使用 "export VAR" 将变量设定为环境变量,以便让命令和递归调用的 make 命令能接收到参数。例如: 使用 GCC C_INCLUDE_PATH 环境变量来代替 -I 参数。C_INCLUDE_PATH := ./lib:/usr/include:/usr/local/includeexport C_INCLUDE_PATH4. 条件没有条件判断是不行滴。CFLAGS = -Wall -std=c99 $(INC_PATHS)ifdef DEBUG    CFLAGS += -gelse    CFLAGS += -O3endif类似的还有: ifeq、ifneq、ifndef格式: ifeq (ARG1, ARG2) 或 ifeq "ARG1" "ARG2"# DEBUG == 1ifeq "$(DEBUG)" "1"    ...else    ...endif# DEBUG 不为空ifneq ($(DEBUG), )    ...else    ...endif实际上,我们可以用 if 函数来代替。相当于编程语言中的三元表达式 "?:"。CFLAGS = -Wall $(if $(DEBUG), -g, -O3) -std=c99 $(INC_PATHS)5. 函数*nix 下的 "配置" 都有点 "脚本语言" 的感觉。make 支持函数的使用,调用方法 "$(function args)" 或 "${function args}"。多个参数之间用 "," (多余的空格可能会成为参数的一部分)。例如: 将 "Hello, World!" 替换成 "Hello, GNU Make!"。A = Hello, World!all:    @echo $(subst World, GUN Make, $(A))注意: 字符串没有用引号包含起来,如果字符串中有引号字符,使用 "/" 转义。5.1 foreach这个 foreach 很好,执行结果输出 "[1] [2] [3]"。A = 1 2 3all:    @echo $(foreach x,$(A),[$(x)])5.2 call我们还可以自定义一个函数,其实就是用一个变量来代替复杂的表达式,比如对上面例子的改写。A = x y zfunc = $(foreach x, $(1), [$(x)])all:    @echo $(call func, $(A))    @echo $(call func, 1 2 3)传递的参数分别是 "$(1), $(2) ..."。用 define 可以定义一个更复杂一点的多行函数。A = x y zdefine func    echo "$(2): $(1) -> $(foreach x, $(1), [$(x)])"endefall:    @$(call func, $(A), char)    @$(call func, 1 2 3, num)输出:$ make char:  x y z ->  [x]  [y]  [z] num:  1 2 3 ->  [1]  [2]  [3]5.3 evaleval 函数的作用是动态生成 Makefile 内容。define func    $(1) = $(1)...endef$(eval $(call func, A))$(eval $(call func, B))all:    @echo $(A) $(B)上面例子的执行结果实际上是 "动态" 定义了两个变量而已。当然,借用 foreach 可以更紧凑一些。$(foreach x, A B, $(eval $(call func, $(x))))5.4 shell执行 shell 命令,这个非常实用。A = $(shell uname)all:    @echo $(A)更多的函数列表和详细信息请参考相关文档。6. 包含include 指令会读取其他的 Makefile 文件内容,并在当前位置展开。通常使用 ".mk" 作为扩展名,支持文件名通配符,支持相对和绝对路径。7. 执行Makefile 常用目标名: all: 默认目标。clean: 清理项目文件的伪目标。install: 安装(拷贝)编译成功的项目文件。tar: 创建源码压缩包。dist: 创建待发布的源码压缩包。tags: 创建 VIM 使用的 CTAGS 文件。make 常用命令参数: -n: 显示待执行的命令,但不执行。-t: 更新目标文件时间戳,也就是说就算依赖项被修改,也不更新目标文件。-k: 出错时,继续执行。-B: 不检查依赖列表,强制更新目标。-C: 执行 make 前,进入特定目录。让我们可以在非 Makefile 目录下执行 make 命令。-e: 使用系统环境变量覆盖同名变量。-i: 忽略命令错误。相当于 "-" 前缀。-I: 指定 include 包含文件搜索目录。-p: 显示所有 Makefile 和 make 的相关参数信息。-s: 不显示执行的命令行。相当于 "@" 前缀。顺序执行多个目标:$ make clean debug作者:unbutun 发表于2011-6-28 21:55:00 原文链接阅读:5 评论:0 查看评论Ubuntu/Linux Tips (shell 快捷键)收集常用技巧,备忘。不定期更新。1. 快捷键终端快捷键:CTRL + C: 停止CTRL + Z: 切换到后台CTRL + D: 注销当前会话CTRL + W: 删除光标前的命令参数CTRL + U: 删除光标前的所有字符CTRL + K: 删除光标后的所有字符CTRL + A: 将光标移到最前CTRL + E: 将光标移到末尾CTRL + L: 清屏CTRL + R: 搜索历史命令2. 文件管理文件搜索:$ find . -name "*.py[co]"                      # 按通配符搜索$ find . -iregex '.*/index.*'                  # 使用正则表达式搜索 (包含完整路径匹配,区分大小写用 regex)$ find . -type d                               # 搜索目录类型 (类型 f, d ...)$ find . -type f -exec ls -l {} /;             # 查找并直接执行命令$ find . -type f -perm +0100 | xargs ls -l     # 查找具有执行权限的普通文件$ find . -name "*.py" | xargs grep -n main     # 按内容搜索$ find . -name "*.py[co]" | xargs rm -rf       # 批量删除$ find . -type f -size +10k | xargs ls -lh     # 大于10kb的文件 (单位 k, M, G, T, P)。$ find . -type f -mtime -2d | xargs ls -l      # 最近两天被修改的文件 (单位 s, m, h, d, w),没有被修改使用 +2d。查看文件头/尾 n 行:$ head -n 5 test.txt$ tail -n 5 test.txt显示/分页显示文件内容:$ cat test.txt$ less test.txt实时刷新文件内容变更(适合监控日志文件变化,调试的时候很有用):$ tail -f test.txt$ less +F test.txt查看文件类型:$ file test.txt3. 系统管理后台运行程序,不随终端会话关闭: nohup$ nohup cat a.txt &$ nohup cat a.txt >/dev/null 2>&1 &终止进程: kill killall$ kill 1267 1268 1269$ kill -INT 1267$ killall python$ killall -INT python4. 网络管理显示网络状态: netstat$ netstat -lp   # 显示监听$ netstat -lpn  # 显示监听端口$ netstat -t    # 显示当前连接动态查看网站路由: mtr$ mtr www.rainsts.netDNS 查询: dig$ dig www.rainsts.netIP 地址配置: ifconfig$ ifconfig$ ip a简易 TCP 监听和连接测试工具(可双向发送数据): nc$ nc -l 8000         # 监听$ nc localhost 8000  # 客户端5. 系统安全6. 压缩备份压缩/接压缩: tar$ tar czf test.tar.gz ./test$ tar czf test.tar.gz a.txt b.txt c.txt  # 压缩多个路径$ tar tf test.tar.gz                     # 查看压缩包内容$ tar xf test.tar.gz$ tar xf test.tar.gz -C ~/test           # 解压缩到指定目录7. 系统帮助系统手册: man$ man -k printf # 模糊搜索$ man -f printf # 精确搜索8. 相关软件Putty:CTRL + S: 屏蔽控制台输出(比如需要输入一些敏感信息)CTRL + Q: 恢复控制台输出作者:unbutun 发表于2011-6-28 21:49:00 原文链接阅读:11 评论:0 查看评论Git Commands1. 系统设置通常情况下,我们只需简单设置用户信息和着色即可。$ git config --global user.name "Q.yuhen"$ git config --global user.email qyuhen@abc.com$ git config --global color.ui true 可以使用 "--list" 查看当前设置。$ git config --list2. 初始化创建项目目录,然后执行 "git init" 初始化。这会在项目目录创建 ".git" 目录,即为元数据信息所在。$ git init通常我们还需要创建一个忽略配置文件 ".gitignore",并不是什么都需要加到代码仓库中的。$ cat > .gitignore << end> *.[oa]> *.so> *~> !a.so> test> tmp/> end如果作为 Server 存在,那么可以忽略工作目录,以纯代码仓库形式存在。$ git --bare init在客户端,我们可以调用 "clone" 命令克隆整个项目。支持 SSH / HTTP/ GIT 等协议。$ git clone ssh://user@server:3387/git/myproj$ git clone git://github.com/schacon/grit.git mygrit3. 基本操作Git 分为 "工作目录"、"暂存区"、"代码仓库" 三个部分。(1) 添加文件通过 "git add <file>" 被添加到暂存区,如此暂存区将拥有一份文件快照。$ git add .$ git add file1 file2$ git add *.c"git add" 除了添加新文件到暂存区进行跟踪外,还可以刷新已被跟踪文件的快照。需要注意的是,被提交到代码仓库的是暂存区的快照,而不是工作目录中的文件。(2) 提交"git commit -m <message>" 命令将暂存区的快照提交到代码仓库。$ git commit -m "message"在执行 commit 提交时,我们通常会直接使用 "-a" 参数。该参数的含义是:刷新暂存区快照,提交时同时移除被删除的文件。但该参数并不会添加未被跟踪的新文件,依然需要执行 "git add <file>" 操作。$ git commit -am "message"(3) 状态可以使用 "git status" 查看暂存区状态,通常包括 "当前工作分支(Branch)"、"被修改的已跟踪文件(Changed but not updated)",以及 "未跟踪的新文件(Untracked files)" 三部分信息。$ git status# On branch master# Changed but not updated:#   (use "git add <file>..." to update what will be committed)#   (use "git checkout -- <file>..." to discard changes in working directory)##       modified:   readme## Untracked files:#   (use "git add <file>..." to include in what will be committed)##       installno changes added to commit (use "git add" and/or "git commit -a")(4) 比较要比较三个区域的文件差别,需要使用 "git diff" 命令。使用 "git diff [file]" 查看工作目录和暂存区的差异。使用 "git diff --staged [file]" 或 "git diff --cached [file]" 查看暂存区和代码仓库的差异。$ git diff readmediff --git a/readme b/readmeindex e69de29..df8285e 100644--- a/readme+++ b/readme@@ -0,0 +1,2 @@+1111111111111111111+(5) 撤销作为代码管理工作,我们随时可以 "反悔"。使用 "git reset HEAD <filename>" 命令可以取消暂存区的文件快照(即恢复成最后一个提交版本),这不会影响工作目录的文件修改。使用 "git checkout -- <filename>" 可以使用暂存区快照恢复工作目录文件,工作目录的文件修改被抛弃。$ git chekcout -- readme在 Git 中 "HEAD" 表示仓库中最后一个提交版本,"HEAD^" 是倒数第二个版本,"HEAD~2" 则是更老的版本。我们可以直接 "签出" 代码仓库中的某个文件版本到工作目录,该操作同时会取消暂存区快照。$ git checkout HEAD^ readme如果想将整个项目回溯到以前的某个版本,可以使用 "git reset"。可以选择的参数包括默认的 "--mixed" 和 "--hard",前者不会取消工作目录的修改,而后者则放弃全部的修改。该操作会丢失其后的日志。$ git reset --hard HEAD^(6) 日志每次提交都会为整个项目创建一个版本,我们可以通过日志来查看相关信息。参数 "git log -p" 可以查看详细信息,包括修改的内容。参数 "git log -2" 查看最后两条日志。参数 "git log --stat" 可以查看统计摘要。$ git log --stat -2 -pcommit c11364da1bde38f55000bc6dea9c1dda426c00f9Author: Q.yuhen <qyuhen@hotmail.com>Date:   Sun Jul 18 15:53:55 2010 +0800    b--- 0 files changed, 0 insertions(+), 0 deletions(-)diff --git a/install b/installnew file mode 100644index 0000000..e69de29commit 784b289acc8dccd1d2d9742d17f586ccaa56a3f0Author: Q.yuhen <qyuhen@hotmail.com>Date:   Sun Jul 18 15:33:24 2010 +0800    a--- 0 files changed, 0 insertions(+), 0 deletions(-)diff --git a/readme b/readmenew file mode 100644index 0000000..e69de29(7) 重做马有失蹄,使用 "git commit --amend" 可以重做最后一次提交。$ git commit --amend -am "b2"[master 6abac48] b2 0 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 abc create mode 100644 install$ git logcommit 6abac48c014598890c6c4f47b4138f6be020e403Author: Q.yuhen <qyuhen@hotmail.com>Date:   Sun Jul 18 15:53:55 2010 +0800    b2commit 784b289acc8dccd1d2d9742d17f586ccaa56a3f0Author: Q.yuhen <qyuhen@hotmail.com>Date:   Sun Jul 18 15:33:24 2010 +0800    a(8) 查看使用 "git show" 可以查看日志中文件的变更信息,默认显示最后一个版本(HEAD)。$ git show readme $ git show HEAD^ readme(9) 标签可以使用标签(tag)对最后提交的版本做标记,如此可以方便记忆和操作,这通常也是一个里程碑的标志。$ git tag v0.9$ git tagv0.9$ git show v0.9commit 3fcdd49fc0f0a45cd283a86bc743b4e5a1dfdf5dAuthor: Q.yuhen <qyuhen@hotmail.com>Date:   Sun Jul 18 14:53:55 2010 +0800...可以直接用标签号代替日志版本号进行操作。$ git log v0.9commit 3fcdd49fc0f0a45cd283a86bc743b4e5a1dfdf5dAuthor: Q.yuhen <qyuhen@hotmail.com>Date:   Sun Jul 18 14:53:55 2010 +0800    a4. 工作分支用 Git 一定得习惯用分支进行工作。使用 "git branch <name>" 创建分支,还可以创建不以当前版本为起点的分支 "git branch <name> HEAD^"。使用 "git checkout <name>" 切换分支。$ git branch yuhen$ git checkout yuhenSwitched to branch 'yuhen'$ git branch  master* yuhen使用 "git chekcout -b <name>" 一次完成分支创建和切换操作。$ git checkout -b yuhenSwitched to a new branch 'yuhen'$ git branch  master* yuhen在分支中完成提交,然后切换回主分支进行合并(git merge)和删除(git branch -d <name>)操作。$ git checkout masterSwitched to branch 'master'$ git merge yuhenUpdating 6abac48..7943312Fast-forward 0 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 abc.txt$ git branch -d yuhenDeleted branch yuhen (was 7943312).$ git branch* master附注: 如果当前工作目录有未提交的内容,直接切换到其他分支会将变更一同带入。5. 服务器(1) 首先克隆服务器代码仓库。$ git clone git@192.168.1.202:/git.server/project1 # SSH完成克隆后,可以用 origin 来代替服务器地址。使用 "git remote" 命令查看相关信息。$ git remoteorigin$ git remote show origin* remote origin  Fetch URL: ...  Push  URL: ...  HEAD branch: master  Remote branch:    master tracked  Local branch configured for 'git pull':    master merges with remote master  Local ref configured for 'git push':    master pushes to master (up to date)还可以创建新的 remote 设置。$ git remote add project1 git@192.168.1.202:/git.server/project1$ git remoteoriginproject1$ git remote rm project1(2) 在将代码提交(push)到服务器之前,首先要确认相关更新已经合并(merge)到主分支(master)。还应该先从服务器刷新(pull)最新代码,以确保自己的提交不会和别人最新提交的代码冲突。$ git pull origin master$ git push origin master(3) 要提交标签到服务器,需要额外操作 (先执行 git push 提交,然后再执行该指令)。$ git push origin --tags6. 管理检查损坏情况。$ git fsck清理无用数据。$ git gc----------- 分隔线 -----------整理一下各种常用的命令,便于翻阅。作者:unbutun 发表于2011-6-28 21:48:00 原文链接阅读:8 评论:0 查看评论Git Server建立一个 Git 代码共享仓库服务器。1. 服务器通常用 SSH 协议即可,我们应该为 Git 创建一个专用账号。$ sudo useradd git$ sudo passwd gitEnter new UNIX password:Retype new UNIX password:passwd: password updated successfully创建一个用来保存代码仓库的目录,注意赋予 git 账号读写权限。$ sudo mkdir -p /var/git.server/project1$ cd /var/git.server$ sudo chown git project1$ sudo chgrp git project1$ ls -ltotal 4drwxr-xr-x 2 git git 4096 2010-05-17 00:55 project1初始化 project1,注意在服务器上我们无需保留工作目录,因此创建一个纯粹(bare)的代码仓库。$ cd project1/$ sudo su git$ pwd/var/git.server/project1$ git --bare initInitialized empty Git repository in /var/git.server/project1/$ ls -ltotal 32drwxr-xr-x 2 git git 4096 2010-05-17 00:59 branches-rw-r--r-- 1 git git   66 2010-05-17 00:59 config-rw-r--r-- 1 git git   73 2010-05-17 00:59 description-rw-r--r-- 1 git git   23 2010-05-17 00:59 HEADdrwxr-xr-x 2 git git 4096 2010-05-17 00:59 hooksdrwxr-xr-x 2 git git 4096 2010-05-17 00:59 infodrwxr-xr-x 4 git git 4096 2010-05-17 00:59 objectsdrwxr-xr-x 4 git git 4096 2010-05-17 00:59 refs$ exit我们在服务器上克隆一份用于管理和测试(应该禁止直接操作服务器仓库目录)。$ git clone /var/git.server/project1/Initialized empty Git repository in /home/yuhen/project1/.git/warning: You appear to have cloned an empty repository.$ ls -al project1total 12drwxr-xr-x  3 yuhen yuhen 4096 2010-05-17 01:02 .drwxr-xr-x 10 yuhen yuhen 4096 2010-05-17 01:02 ..drwxr-xr-x  7 yuhen yuhen 4096 2010-05-17 01:02 .git我们添加点项目初始化文件。$ cd project1$ cat > .gitingore << end> *~> *.swp> end$ touch README$ git status# On branch master## Initial commit## Untracked files:#   (use "git add <file>..." to include in what will be committed)##       .gitingore#       READMEnothing added to commit but untracked files present (use "git add" to track)$ git add .$ git commit -am "Start"[master (root-commit) 723471e] Start 1 files changed, 2 insertions(+), 0 deletions(-) create mode 100644 .gitingore create mode 100644 README我们向服务器提交第一个版本。$ git push git@localhost:/var/git.server/project1/ masterCounting objects: 4, done.Delta compression using up to 2 threads.Compressing objects: 100% (2/2), done.Writing objects: 100% (4/4), 258 bytes, done.Total 4 (delta 0), reused 0 (delta 0)To git@localhost:/var/git.server/project1/ * [new branch]      master -> master通常情况下,我们可以用 origin 来代替服务器地址,不过当前测试账号没有写 git.server/project1 的权限,因此用 ssh 路径。同时需要指定 branch。2. 客户端好了,现在作为一个普通程序员,我们开始为 project1 项目工作。$ git clone git@192.168.1.202:/var/git.server/project1Initialized empty Git repository in /home/yuhen/project1/.git/git@192.168.1.202's password: remote: Counting objects: 4, done.remote: Compressing objects: 100% (2/2), done.remote: Total 4 (delta 0), reused 0 (delta 0)Receiving objects: 100% (4/4), done.$ ls -al project1total 16drwxr-xr-x  3 yuhen yuhen 4096 2010-05-17 01:11 .drwxr-xr-x 27 yuhen yuhen 4096 2010-05-17 01:10 ..drwxr-xr-x  8 yuhen yuhen 4096 2010-05-17 01:11 .git-rw-r--r--  1 yuhen yuhen    9 2010-05-17 01:11 .gitingore-rw-r--r--  1 yuhen yuhen    0 2010-05-17 01:11 README代码已经克隆回来了,我们添加或修改一些文件。$ touch INSTALL$ git status# On branch master# Untracked files:#   (use "git add <file>..." to include in what will be committed)##    INSTALLnothing added to commit but untracked files present (use "git add" to track)$ git add .$ git commit -am "INSTALL"[master b85e275] INSTALL Committer: yuhen <yuhen@yuhen-desktop.(none)> 0 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 INSTALL在将代码提交(push)到服务器之前,首先要确认相关更新已经合并(merge)到 master 了,还应该先从服务器刷新(pull)最新代码,以确保自己的提交不会和别人最新提交的代码冲突。$ git pull origin mastergit@192.168.1.202's password: remote: Counting objects: 3, done.remote: Compressing objects: 100% (2/2), done.remote: Total 2 (delta 0), reused 0 (delta 0)Unpacking objects: 100% (2/2), done.From 192.168.1.202:/var/git.server/project1 * branch            master     -> FETCH_HEADMerge made by recursive. 0 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 HISTORY$ git push origin mastergit@192.168.1.202's password: Could not chdir to home directory /home/git: No such file or directoryCounting objects: 6, done.Delta compression using up to 2 threads.Compressing objects: 100% (4/4), done.Writing objects: 100% (4/4), 474 bytes, done.Total 4 (delta 2), reused 0 (delta 0)To git@192.168.1.202:/var/git.server/project1   c4d7b1e..ee8cfb3  master -> master$ git logcommit ee8cfb3d14eed091e6f96d60af68ec07c05ab09dMerge: b85e275 c4d7b1eAuthor: yuhen <yuhen@yuhen-desktop.(none)>Date:   Mon May 17 01:17:49 2010 +0800    Merge branch 'master' of 192.168.1.202:/var/git.server/project1commit c4d7b1e796cf52e0b600f2c7e992f304052fa8c1Author: Q.yuhen <qyuhen@hotmail.com>Date:   Mon May 17 01:17:26 2010 +0800    HISTORYcommit b85e275b52812e3d9ac36da78fb8cc924380a58cAuthor: yuhen <yuhen@yuhen-desktop.(none)>Date:   Mon May 17 01:13:35 2010 +0800    INSTALLcommit 723471e3421d7fdfa80bf31e5c27f5a174e95afdAuthor: Q.yuhen <qyuhen@hotmail.com>Date:   Mon May 17 01:05:53 2010 +0800    Start我们应该避免频繁向服务器提交代码,而是在一个相对稳定的版本测试通过后再进行。基本操作就是这些了,当然我们还可以提供只读账号或者 HTTP 访问协议,相关内容可参考《Pro Git》。作者:unbutun 发表于2011-6-28 21:48:00 原文链接阅读:9 评论:0 查看评论Git Tips1. 删除文件除了用 "rm" 删除工作目录中的文件外,还得用 "git rm <file>" 删除代码仓库中的文件。$ rm INSTALL $ git status# On branch master# Changed but not updated:#   (use "git add/rm <file>..." to update what will be committed)#   (use "git checkout -- <file>..." to discard changes in working directory)##    deleted:    INSTALL#no changes added to commit (use "git add" and/or "git commit -a")$ git rm INSTALLrm 'doc/INSTALL'$ git commit -am "rm INSTALL"[master 8600e47] rm INSTALL 0 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 doc/INSTALL当然,版本管理工具的一个好处就是有后悔药卖。$ git checkout HEAD^ -- INSTALL$ lsINSTALL  README$ git status# On branch master# Changes to be committed:#   (use "git reset HEAD <file>..." to unstage)##    new file:   INSTALL#如果仅此仓库移除,但保留工作目录中的文件,可以直接用 "git rm --cached <file>",遗留的文件会变成未跟踪状态。2. 移动文件和删除文件的做法类似。$ mv HISTORY doc/$ git status# On branch master# Changed but not updated:#   (use "git add/rm <file>..." to update what will be committed)#   (use "git checkout -- <file>..." to discard changes in working directory)##    deleted:    HISTORY## Untracked files:#   (use "git add <file>..." to include in what will be committed)##    doc/HISTORYno changes added to commit (use "git add" and/or "git commit -a")$ git add .$ git commit -am "mv HISTORY"[master 716af03] mv HISTORY 1 files changed, 0 insertions(+), 0 deletions(-) rename HISTORY => doc/HISTORY (100%)3. 重新提交如果最后一次的提交需要修正什么,那么可以用 "--amend" 参数。$ touch a.txt$ git add .$ git commit -am "b.txt"[master b66690c] b.txt 0 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 a.txtyuhen@yuhen-desktop:~/myproject$ git logcommit b66690cc4353e95fa52cf6cf2e8a3210c1ace057Author: Q.yuhen <yuhen@yuhen-desktop.(none)>Date:   Tue Apr 20 19:37:23 2010 +0800    b.txtcommit 716af03e2ebafd8c47bf941e0185b8b67adcd20eAuthor: Q.yuhen <yuhen@yuhen-desktop.(none)>Date:   Tue Apr 20 19:35:17 2010 +0800    move HISTORY很显然,注释 "b.txt" 写错了。重来吧~~~$ git commit --amend -am "a.txt"[master a93a7cd] a.txt 0 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 a.txt$ git logcommit a93a7cdea6d9344d975c58332064ae48b29fb68fAuthor: Q.yuhen <yuhen@yuhen-desktop.(none)>Date:   Tue Apr 20 19:37:23 2010 +0800    a.txtcommit 716af03e2ebafd8c47bf941e0185b8b67adcd20eAuthor: Q.yuhen <yuhen@yuhen-desktop.(none)>Date:   Tue Apr 20 19:35:17 2010 +0800    move HISTORY最后一条提交日志被替换了。4. 恢复单个文件可以用 "git checkout ..." 签出以前的某个 "提交版本" 。$ cat main.c#include <stdio.h>#include <stdlib.h>#include <string.h>int main(int argc, char* argv[]){    printf("Hello, World!/n");    return EXIT_SUCCESS;}$ git show HEAD^ main.ccommit 65bd689c3a2b97c598ee2e66c453cc526b63b892Author: Q.yuhen <yuhen@yuhen-desktop.(none)>Date:   Tue Apr 20 19:52:16 2010 +0800    add main.cdiff --git a/main.c b/main.cnew file mode 100644index 0000000..c50fab4--- /dev/null+++ b/main.c@@ -0,0 +1,9 @@+#include <stdio.h>+#include <stdlib.h>+#include <string.h>++int main(int argc, char* argv[])+{+    return EXIT_SUCCESS;+}+$ git checkout HEAD^ -- main.c$ cat main.c#include <stdio.h>#include <stdlib.h>#include <string.h>int main(int argc, char* argv[]){    return EXIT_SUCCESS;}也可以用 "git reset HEAD <file>" 重置已添加到暂存区(stage)但未提交(commit)的文件。4. 查看文件详细信息用 "git show" 查看某个提交版本的具体信息,或者 "git diff" 比较差异。$ git show main.ccommit 017c106d3bb9c423dead6d732db1651119b6423cAuthor: Q.yuhen <yuhen@yuhen-desktop.(none)>Date:   Tue Apr 20 20:04:12 2010 +0800    update main.cdiff --git a/main.c b/main.cindex c50fab4..a6bb490 100644--- a/main.c+++ b/main.c@@ -4,6 +4,7 @@  int main(int argc, char* argv[]) {+    printf("Hello, World!/n");    return EXIT_SUCCESS; }$ git diff HEAD main.cdiff --git a/main.c b/main.cindex a6bb490..c50fab4 100644--- a/main.c+++ b/main.c@@ -4,7 +4,6 @@  int main(int argc, char* argv[]) {-    printf("Hello, World!/n");    return EXIT_SUCCESS; } 5. 查看提交日志"git log -3" 查看最后 3 条提交信息。$ git log -3commit 017c106d3bb9c423dead6d732db1651119b6423cAuthor: Q.yuhen <yuhen@yuhen-desktop.(none)>Date:   Tue Apr 20 20:04:12 2010 +0800    update main.ccommit 65bd689c3a2b97c598ee2e66c453cc526b63b892Author: Q.yuhen <yuhen@yuhen-desktop.(none)>Date:   Tue Apr 20 19:52:16 2010 +0800    add main.ccommit 3725c8d49a61bb6d0bb18d7727fcaafe32f510fcAuthor: Q.yuhen <yuhen@yuhen-desktop.(none)>Date:   Tue Apr 20 16:55:50 2010 +0800    init还可以用 "--stat" 显示简单的提交统计信息。$ git log -3 --statcommit 017c106d3bb9c423dead6d732db1651119b6423cAuthor: Q.yuhen <yuhen@yuhen-desktop.(none)>Date:   Tue Apr 20 20:04:12 2010 +0800    update main.c main.c |    1 + 1 files changed, 1 insertions(+), 0 deletions(-)commit 65bd689c3a2b97c598ee2e66c453cc526b63b892Author: Q.yuhen <yuhen@yuhen-desktop.(none)>Date:   Tue Apr 20 19:52:16 2010 +0800    add main.c main.c |    9 +++++++++ 1 files changed, 9 insertions(+), 0 deletions(-)commit 3725c8d49a61bb6d0bb18d7727fcaafe32f510fcAuthor: Q.yuhen <yuhen@yuhen-desktop.(none)>Date:   Tue Apr 20 16:55:50 2010 +0800    init .gitignore |    5 +++++ 1 files changed, 5 insertions(+), 0 deletions(-)参数 "-p" 显示修改的详细信息。$ git log -1 -pcommit 017c106d3bb9c423dead6d732db1651119b6423cAuthor: Q.yuhen <yuhen@yuhen-desktop.(none)>Date:   Tue Apr 20 20:04:12 2010 +0800    update main.cdiff --git a/main.c b/main.cindex c50fab4..a6bb490 100644--- a/main.c+++ b/main.c@@ -4,6 +4,7 @@  int main(int argc, char* argv[]) {+    printf("Hello, World!/n");    return EXIT_SUCCESS; }6. 初始化全局设置用户名、联系方式以及着色显示都很要紧。$ git config --global user.name "Q.yuhen"$ git config --global user.email qyuhen@abc.com$ git config --global color.ui true 可以用 "--list" 参数查看全局设置。$ git config --listuser.name=Q.yuhenuser.email=qyuhen@abc.comcolor.ui=truecore.repositoryformatversion=0core.filemode=truecore.bare=falsecore.logallrefupdates=true更多设置可参考 《Pro Git: 配置 Git》。作者:unbutun 发表于2011-6-28 21:47:00 原文链接阅读:7 评论:0 查看评论Git BasicsGit 的好处还是看官方文档吧,我就不瞎扯了。我只是按照我个人的习惯简单走一下流程,记录一下。1. 创建项目目录,建立 Git 仓库。$ mkdir myproject$ cd myproject$ git init # 初始化 git 仓库Initialized empty Git repository in /home/yuhen/myproject/.git/从此,myproject 就是工作目录,而 git 创建的 .git 隐藏目录就是代码仓库了。2. 建立忽略配置文件。$ cat > .gitignore << end> *.[oa]> *.so> *~> !a.so> test> tmp/> end支持匹配符和正则表达式,支持 "#" 注释,支持用 "/" 结尾表示路径。还可以用 "!" 取反,比如前面的 "*.so" 规则忽略所有 .so 文件,然后用 "!a.so" 表示特例。balabala... 等等...3. 好了,开始第一个 commit 吧。$ git status # 查看当前 track 状态# On branch master## Initial commit## Untracked files:#   (use "git add <file>..." to include in what will be committed)##    .gitignorenothing added to commit but untracked files present (use "git add" to track)$ git add . # 添加所有新增文件$ git commit -am "init" # 提交必须提供一个注释,否则无法执行。[master (root-commit) 3725c8d] init 1 files changed, 5 insertions(+), 0 deletions(-) create mode 100644 .gitignore4. 创建第一个工作分支。$ git branch yuhen # 创建新的分支$ git branch # 查看当前所有分支* master  yuhen$ git checkout yuhen # 切换到新的工作分支Switched to branch 'yuhen'$ git branch # 确认一下  master* yuhen也可以用 "git checkout -b yuhen" 一次完成创建和切换分支的工作。$ git checkout -b yuhenSwitched to a new branch 'yuhen'$ git branch  master* yuhen5. 开始工作,添加或编辑文件。$ cat > main.c << end> #include <stdio.h>> #include <stdlib.h>> > int main(int argc, char* argv[])> {>     return EXIT_SUCCESS;> }> > end$ mkdir doc$ cd doc$ cat > README << end> readme file> end$ cat > INSTALL << end> install...> end$ cd ..$ git status # 我们可以查看所有被 git 跟踪的改变# On branch yuhen# Untracked files:#   (use "git add <file>..." to include in what will be committed)##    doc/#    main.cnothing added to commit but untracked files present (use "git add" to track)没问题的话,就提交到代码仓库吧。$ git add . # 添加新增文件$ git commit -am "main.c, doc/" # 提交[yuhen 42d7d10] main.c, doc/ 3 files changed, 10 insertions(+), 0 deletions(-) create mode 100644 doc/INSTALL create mode 100644 doc/README create mode 100644 main.c$ git log # 查看提交日志commit 42d7d10e008f34bb6b7c9824cbfa6b1c84c008a4Author: Q.yuhen <yuhen@yuhen-desktop.(none)>Date:   Tue Apr 20 17:03:06 2010 +0800    main.c, doc/commit 3725c8d49a61bb6d0bb18d7727fcaafe32f510fcAuthor: Q.yuhen <yuhen@yuhen-desktop.(none)>Date:   Tue Apr 20 16:55:50 2010 +0800    init6. 合并工作分支到 master。$ git checkout master # 切换回主分支Switched to branch 'master'$ git branch* master  yuhen$ git merge yuhen # 将 yuhen 工作分支合并到主分支Updating 3725c8d..42d7d10Fast forward doc/INSTALL |    1 + doc/README  |    1 + main.c      |    8 ++++++++ 3 files changed, 10 insertions(+), 0 deletions(-) create mode 100644 doc/INSTALL create mode 100644 doc/README create mode 100644 main.c$ git branch -d yuhen # 删除工作分支Deleted branch yuhen (was 42d7d10).今天的工作完成了,看看日志。$ git logcommit 42d7d10e008f34bb6b7c9824cbfa6b1c84c008a4Author: Q.yuhen <yuhen@yuhen-desktop.(none)>Date:   Tue Apr 20 17:03:06 2010 +0800    main.c, doc/commit 3725c8d49a61bb6d0bb18d7727fcaafe32f510fcAuthor: Q.yuhen <yuhen@yuhen-desktop.(none)>Date:   Tue Apr 20 16:55:50 2010 +0800    init用一个分支进行工作是个好主意,因为 git 有句话叫做 "丢掉一个烂摊子总比收拾一个烂摊子强"。7. 如果我们发现某次提交有问题,我们可以恢复到以前的某个提交版本。$ vim main.c # 编辑文件$ cat main.c#include <stdio.h>#include <stdlib.h>int main(int argc, char* argv[]){    printf("Hello, World!/n");    return EXIT_SUCCESS;}$ git status # On branch master# Changed but not updated:#   (use "git add <file>..." to update what will be committed)#   (use "git checkout -- <file>..." to discard changes in working directory)##    modified:   main.c#no changes added to commit (use "git add" and/or "git commit -a")$ git commit -am "main.c changed" # 提交[master a79baec] main.c changed 1 files changed, 1 insertions(+), 0 deletions(-)$ git log # 提交成功commit a79baecfecbcedb8545a7ff4aeebba3fe653efa4Author: Q.yuhen <yuhen@yuhen-desktop.(none)>Date:   Tue Apr 20 17:15:51 2010 +0800    main.c changedcommit 42d7d10e008f34bb6b7c9824cbfa6b1c84c008a4Author: Q.yuhen <yuhen@yuhen-desktop.(none)>Date:   Tue Apr 20 17:03:06 2010 +0800    main.c, doc/commit 3725c8d49a61bb6d0bb18d7727fcaafe32f510fcAuthor: Q.yuhen <yuhen@yuhen-desktop.(none)>Date:   Tue Apr 20 16:55:50 2010 +0800    init$ git reset HEAD^ # 恢复到上次某个提交状态,可以是 HEAD^、HEAD~4、commit-id 的头几个字母,还可以是 tag。main.c: locally modifiedyuhen@yuhen-desktop:~/myproject$ git logcommit 42d7d10e008f34bb6b7c9824cbfa6b1c84c008a4Author: Q.yuhen <yuhen@yuhen-desktop.(none)>Date:   Tue Apr 20 17:03:06 2010 +0800    main.c, doc/commit 3725c8d49a61bb6d0bb18d7727fcaafe32f510fcAuthor: Q.yuhen <yuhen@yuhen-desktop.(none)>Date:   Tue Apr 20 16:55:50 2010 +0800    init$ git status # 看到没有,默认 reset 模式是 --mixed,会保留文件修改。还可以用 --hard 放弃这些修改。# On branch master# Changed but not updated:#   (use "git add <file>..." to update what will be committed)#   (use "git checkout -- <file>..." to discard changes in working directory)##    modified:   main.c#no changes added to commit (use "git add" and/or "git commit -a")$ cat main.c # 修改还被保留着呢。#include <stdio.h>#include <stdlib.h>int main(int argc, char* argv[]){    printf("Hello, World!/n");    return EXIT_SUCCESS;}8. 工作了 n 天了,总算进入某个阶段性版本了。$ git tag v0.9 # 创建简单标签$ git tag # 显示所有标签v0.9$ git log v0.9 # 用标签显示提交状态commit 42d7d10e008f34bb6b7c9824cbfa6b1c84c008a4Author: Q.yuhen <yuhen@yuhen-desktop.(none)>Date:   Tue Apr 20 17:03:06 2010 +0800    main.c, doc/commit 3725c8d49a61bb6d0bb18d7727fcaafe32f510fcAuthor: Q.yuhen <yuhen@yuhen-desktop.(none)>Date:   Tue Apr 20 16:55:50 2010 +0800    init$ git show --stat v0.9 # 用标签显示提交基本信息commit 42d7d10e008f34bb6b7c9824cbfa6b1c84c008a4Author: Q.yuhen <yuhen@yuhen-desktop.(none)>Date:   Tue Apr 20 17:03:06 2010 +0800    main.c, doc/ doc/INSTALL |    1 + doc/README  |    1 + main.c      |    8 ++++++++ 3 files changed, 10 insertions(+), 0 deletions(-)-------------- 分隔线 ---------------------推荐 《Pro Git》。作者:unbutun 发表于2011-6-28 21:46:00 原文链接阅读:11 评论:0 查看评论GDB 调试演示 (very good)作为内置和最常用的调试器,GDB 显然有着无可辩驳的地位。熟练使用 GDB,就好像所有 Linux 下的开发人员建议你用 VIM 一样,是个很 "奇怪" 的情节。测试用源代码。#include <stdio.h>int test(int a, int b){    int c = a + b;    return c;}int main(int argc, char* argv[]){    int a = 0x1000;    int b = 0x2000;    int c = test(a, b);    printf("%d/n", c);    printf("Hello, World!/n");    return 0;}编译命令 (注意使用 "-g" 参数生成调试符号):$ gcc -g -o hello hello.c开始调试:$ gdb helloGNU gdb 6.8-debianCopyright (C) 2008 Free Software Foundation, Inc.License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>This is free software: you are free to change and redistribute it.There is NO WARRANTY, to the extent permitted by law.  Type "show copying"and "show warranty" for details.This GDB was configured as "i486-linux-gnu"...(gdb)1. 源码在调试过程中查看源代码是必须的。list (缩写 l) 支持多种方式查看源码。(gdb) l # 显示源代码23    int test(int a, int b)4    {5        int c = a + b;6        return c;7    }89    int main(int argc, char* argv[])10    {11        int a = 0x1000;(gdb) l # 继续显示12        int b = 0x2000;13        int c = test(a, b);14        printf("%d/n", c);1516        printf("Hello, World!/n");17        return 0;18    }(gdb) l 3, 10 # 显示特定范围的源代码3    int test(int a, int b)4    {5        int c = a + b;6        return c;7    }89    int main(int argc, char* argv[])10    {(gdb) l main # 显示特定函数源代码5        int c = a + b;6        return c;7    }89    int main(int argc, char* argv[])10    {11        int a = 0x1000;12        int b = 0x2000;13        int c = test(a, b);14        printf("%d/n", c);可以用如下命令修改源代码显示行数。(gdb) set listsize 502. 断点可以使用函数名或者源代码行号设置断点。(gdb) b main # 设置函数断点Breakpoint 1 at 0x804841b: file hello.c, line 11.(gdb) b 13 # 设置源代码行断点Breakpoint 2 at 0x8048429: file hello.c, line 13.(gdb) b # 将下一行设置为断点 (循环、递归等调试很有用)Breakpoint 5 at 0x8048422: file hello.c, line 12.(gdb) tbreak main # 设置临时断点 (中断后失效)Breakpoint 1 at 0x804841b: file hello.c, line 11.(gdb) info breakpoints # 查看所有断点Num     Type           Disp Enb Address    What2       breakpoint     keep y   0x0804841b in main at hello.c:113       breakpoint     keep y   0x080483fa in test at hello.c:5(gdb) d 3 # delete: 删除断点 (还可以用范围 "d 1-3",无参数时删除全部断点)(gdb) disable 2 # 禁用断点 (还可以用范围 "disable 1-3")(gdb) enable 2 # 启用断点 (还可以用范围 "enable 1-3")(gdb) ignore 2 1 # 忽略 2 号中断 1 次当然少不了条件式中断。(gdb) b test if a == 10Breakpoint 4 at 0x80483fa: file hello.c, line 5.(gdb) info breakpointsNum     Type           Disp Enb Address    What4       breakpoint     keep y   0x080483fa in test at hello.c:5        stop only if a == 10可以用 condition 修改条件,注意表达式不包含 "if"。(gdb) condition 4 a == 30(gdb) info breakpointsNum     Type           Disp Enb Address    What2       breakpoint     keep y   0x0804841b in main at hello.c:11        ignore next 1 hits4       breakpoint     keep y   0x080483fa in test at hello.c:5        stop only if a == 303. 执行通常情况下,我们会先设置 main 入口断点。(gdb) b mainBreakpoint 1 at 0x804841b: file hello.c, line 11.(gdb) r # 开始执行 (Run)Starting program: /home/yuhen/Learn.c/helloBreakpoint 1, main () at hello.c:1111              int a = 0x1000;(gdb) n # 单步执行 (不跟踪到函数内部, Step Over)12              int b = 0x2000;(gdb) n13              int c = test(a, b);(gdb) s # 单步执行 (跟踪到函数内部, Step In)test (a=4096, b=8192) at hello.c:55               int c = a + b;(gdb) finish # 继续执行直到当前函数结束 (Step Out)Run till exit from #0  test (a=4096, b=8192) at hello.c:50x0804843b in main () at hello.c:1313              int c = test(a, b);Value returned is $1 = 12288(gdb) c # Continue: 继续执行,直到下一个断点。Continuing.12288Hello, World!Program exited normally.4. 堆栈查看调用堆栈(call stack)无疑是调试过程中非常重要的事情。(gdb) where # 查看调用堆栈 (相同作用的命令还有 info s 和 bt)#0  test (a=4096, b=8192) at hello.c:5#1  0x0804843b in main () at hello.c:13(gdb) frame # 查看当前堆栈帧,还可显示当前代码#0  test (a=4096, b=8192) at hello.c:55               int c = a + b;(gdb) info frame # 获取当前堆栈帧更详细的信息Stack level 0, frame at 0xbfad3290: eip = 0x80483fa in test (hello.c:5); saved eip 0x804843b called by frame at 0xbfad32c0 source language c. Arglist at 0xbfad3288, args: a=4096, b=8192 Locals at 0xbfad3288, Previous frame's sp is 0xbfad3290 Saved registers:  ebp at 0xbfad3288, eip at 0xbfad328c可以用 frame 修改当前堆栈帧,然后查看其详细信息。(gdb) frame 1#1  0x0804843b in main () at hello.c:1313              int c = test(a, b);(gdb) info frameStack level 1, frame at 0xbfad32c0: eip = 0x804843b in main (hello.c:13); saved eip 0xb7e59775 caller of frame at 0xbfad3290 source language c. Arglist at 0xbfad32b8, args: Locals at 0xbfad32b8, Previous frame's sp at 0xbfad32b4 Saved registers:  ebp at 0xbfad32b8, eip at 0xbfad32bc5. 变量和参数(gdb) info locals # 显示局部变量c = 0(gdb) info args # 显示函数参数(自变量)a = 4096b = 8192我们同样可以切换 frame,然后查看不同堆栈帧的信息。(gdb) p a # print 命令可显示局部变量和参数值$2 = 4096(gdb) p/x a # 十六进制输出 (d: 十进制; u: 十进制无符号; x: 十六进制; o: 八进制; t: 二进制; c: 字符)$10 = 0x1000(gdb) p a + b # 还可以进行表达式计算$5 = 12288set variable 可用来修改变量值。(gdb) set variable a=100(gdb) info argsa = 100b = 81926. 内存及寄存器x 命令可以显示指定地址的内存数据。格式: x/nfu [address]n: 显示内存单位(组或者行)。f: 格式 (除了 print 格式外,还有 字符串s 和 汇编 i)。u: 内存单位 (b: 1字节; h: 2字节; w: 4字节; g: 8字节)。(gdb) x/8w 0x0804843b # 按四字节(w)显示 8 组内存数据0x804843b <main+49>:    0x8bf04589      0x4489f045      0x04c70424      0x048530240x804844b <main+65>:    0xfecbe808      0x04c7ffff      0x04853424      0xfecfe808(gdb) x/8i 0x0804843b # 显示 8 行汇编指令0x804843b <main+49>:    mov    DWORD PTR [ebp-0x10],eax0x804843e <main+52>:    mov    eax,DWORD PTR [ebp-0x10]0x8048441 <main+55>:    mov    DWORD PTR [esp+0x4],eax0x8048445 <main+59>:    mov    DWORD PTR [esp],0x80485300x804844c <main+66>:    call   0x804831c <printf@plt>0x8048451 <main+71>:    mov    DWORD PTR [esp],0x80485340x8048458 <main+78>:    call   0x804832c <puts@plt>0x804845d <main+83>:    mov    eax,0x0(gdb) x/s 0x08048530 # 显示字符串0x8048530:       "%d/n"除了通过 "info frame" 查看寄存器值外,还可以用如下指令。(gdb) info registers # 显示所有寄存器数据eax            0x1000   4096ecx            0xbfad32d0       -1079168304edx            0x1      1ebx            0xb7fa1ff4       -1208344588esp            0xbfad3278       0xbfad3278ebp            0xbfad3288       0xbfad3288esi            0x8048480        134513792edi            0x8048340        134513472eip            0x80483fa        0x80483fa <test+6>eflags         0x286    [ PF SF IF ]cs             0x73     115ss             0x7b     123ds             0x7b     123es             0x7b     123fs             0x0      0gs             0x33     51(gdb) p $eax # 显示单个寄存器数据$11 = 40967. 反汇编我对 AT&T 汇编不是很熟悉,还是设置成 intel 格式的好。(gdb) set disassembly-flavor intel # 设置反汇编格式(gdb) disass main # 反汇编函数Dump of assembler code for function main:0x0804840a <main+0>:    lea    ecx,[esp+0x4]0x0804840e <main+4>:    and    esp,0xfffffff00x08048411 <main+7>:    push   DWORD PTR [ecx-0x4]0x08048414 <main+10>:   push   ebp0x08048415 <main+11>:   mov    ebp,esp0x08048417 <main+13>:   push   ecx0x08048418 <main+14>:   sub    esp,0x240x0804841b <main+17>:   mov    DWORD PTR [ebp-0x8],0x10000x08048422 <main+24>:   mov    DWORD PTR [ebp-0xc],0x20000x08048429 <main+31>:   mov    eax,DWORD PTR [ebp-0xc]0x0804842c <main+34>:   mov    DWORD PTR [esp+0x4],eax0x08048430 <main+38>:   mov    eax,DWORD PTR [ebp-0x8]0x08048433 <main+41>:   mov    DWORD PTR [esp],eax0x08048436 <main+44>:   call   0x80483f4 <test>0x0804843b <main+49>:   mov    DWORD PTR [ebp-0x10],eax0x0804843e <main+52>:   mov    eax,DWORD PTR [ebp-0x10]0x08048441 <main+55>:   mov    DWORD PTR [esp+0x4],eax0x08048445 <main+59>:   mov    DWORD PTR [esp],0x80485300x0804844c <main+66>:   call   0x804831c <printf@plt>0x08048451 <main+71>:   mov    DWORD PTR [esp],0x80485340x08048458 <main+78>:   call   0x804832c <puts@plt>0x0804845d <main+83>:   mov    eax,0x00x08048462 <main+88>:   add    esp,0x240x08048465 <main+91>:   pop    ecx0x08048466 <main+92>:   pop    ebp0x08048467 <main+93>:   lea    esp,[ecx-0x4]0x0804846a <main+96>:   retEnd of assembler dump.可以用 "b *address" 设置汇编断点,然后用 "si" 和 "ni" 进行汇编级单步执行,这对于分析指针和寻址非常有用。8. 进程查看进程相关信息,尤其是 maps 内存数据是非常有用的。(gdb) help info proc statShow /proc process information about any running process.Specify any process id, or use the program being debugged by default.Specify any of the following keywords for detailed info:  mappings -- list of mapped memory regions.  stat     -- list a bunch of random process info.  status   -- list a different bunch of random process info.  all      -- list all available /proc info.(gdb) info proc mappings # 相当于 cat /proc/{pid}/mapsprocess 22561cmdline = '/home/yuhen/Learn.c/hello'cwd = '/home/yuhen/Learn.c'exe = '/home/yuhen/Learn.c/hello'Mapped address spaces:        Start Addr   End Addr       Size     Offset objfile         0x8048000  0x8049000     0x1000          0       /home/yuhen/Learn.c/hello         0x8049000  0x804a000     0x1000          0       /home/yuhen/Learn.c/hello         0x804a000  0x804b000     0x1000     0x1000       /home/yuhen/Learn.c/hello         0x8a33000  0x8a54000    0x21000  0x8a33000           [heap]        0xb7565000 0xb7f67000   0xa02000 0xb7565000        0xb7f67000 0xb80c3000   0x15c000          0      /lib/tls/i686/cmov/libc-2.9.so        0xb80c3000 0xb80c4000     0x1000   0x15c000      /lib/tls/i686/cmov/libc-2.9.so        0xb80c4000 0xb80c6000     0x2000   0x15c000      /lib/tls/i686/cmov/libc-2.9.so        0xb80c6000 0xb80c7000     0x1000   0x15e000      /lib/tls/i686/cmov/libc-2.9.so        0xb80c7000 0xb80ca000     0x3000 0xb80c7000        0xb80d7000 0xb80d9000     0x2000 0xb80d7000        0xb80d9000 0xb80da000     0x1000 0xb80d9000           [vdso]        0xb80da000 0xb80f6000    0x1c000          0      /lib/ld-2.9.so        0xb80f6000 0xb80f7000     0x1000    0x1b000      /lib/ld-2.9.so        0xb80f7000 0xb80f8000     0x1000    0x1c000      /lib/ld-2.9.so        0xbfee2000 0xbfef7000    0x15000 0xbffeb000           [stack]9. 线程可以在 pthread_create 处设置断点,当线程创建时会生成提示信息。(gdb) cContinuing.[New Thread 0xb7e78b70 (LWP 2933)](gdb) info threads # 查看所有线程列表* 2 Thread 0xb7e78b70 (LWP 2933)  test (arg=0x804b008) at main.c:24  1 Thread 0xb7e796c0 (LWP 2932)  0xb7fe2430 in __kernel_vsyscall ()(gdb) where # 显示当前线程调用堆栈#0  test (arg=0x804b008) at main.c:24#1  0xb7fc580e in start_thread (arg=0xb7e78b70) at pthread_create.c:300#2  0xb7f478de in clone () at ../sysdeps/unix/sysv/linux/i386/clone.S:130(gdb) thread 1 # 切换线程[Switching to thread 1 (Thread 0xb7e796c0 (LWP 2932))]#0  0xb7fe2430 in __kernel_vsyscall ()(gdb) where # 查看切换后线程调用堆栈#0  0xb7fe2430 in __kernel_vsyscall ()#1  0xb7fc694d in pthread_join (threadid=3085405040, thread_return=0xbffff744) at pthread_join.c:89#2  0x08048828 in main (argc=1, argv=0xbffff804) at main.c:3610. 其他调试子进程。(gdb) set follow-fork-mode child临时进入 Shell 执行命令,Exit 返回。(gdb) shell调试时直接调用函数。(gdb) call test("abc")使用 "--tui" 参数,可以在终端窗口上部显示一个源代码查看窗。$ gdb --tui hello查看命令帮助。(gdb) help b最后就是退出命令。(gdb) q和 Linux Base Shell 习惯一样,对于记不住的命令,可以在输入前几个字母后按 Tab 补全。----------- 分隔线 ---------------GDB 还有很多指令,功能也异常强大。不过对于熟悉了 VS 那种豪华 IDE 的人来说,这种命令行调试是种巨大的痛苦。尽管我个人建议多用 GDB,但也不反对用 GUI 调试器来加快调试进程。Nemiver 就不错,推荐一下。作者:unbutun 发表于2011-6-28 21:46:00 原文链接阅读:14 评论:0 查看评论技巧:用 C 语言实现程序的多态性王 广, 硕士研究生, 中国科学技术大学 简介: 使用面向对象的语言可以实现多态,并且在很大程度上降低了代码的复杂性。对于面向过程的 C 语言同样可以实现多态,本文将着重介绍 C 语言是如何实现多态的。本文的标签:  c, c/cpp 标记本文! 发布日期: 2010 年 8 月 17 日 级别: 初级 访问情况 4212 次浏览 建议: 23 (查看或添加评论) 平均分 (共 41 个评分 ) 前言:关于多态,关于 C多态 (polymorphism) 一词最初来源于希腊语 polumorphos,含义是具有多种形式或形态的情形。在程序设计领域,一个广泛认可的定义是“一种将不同的特殊行为和单个泛化记号相关联的能力”。然而在人们的直观感觉中,多态的含义大约等同于“同一个方法对于不同类型的输入参数均能做出正确的处理过程,并给出人们所期望获得的结果”,也许这正体现了人们对于多态性所能达到的效果所寄予的期望:使程序能够做到越来越智能化,越来越易于使用,越来越能够使设计者透过形形色色的表象看到代码所要触及到的问题本质。作为读者的你或许对于面向对象编程已有着精深的见解,或许对于多态的方便与神奇你也有了深入的认识。这时候你讶异的开始质疑了:“多态,那是面向对象编程才有的技术,C 语言是面向过程的啊!”而我想说的是,C 语言作为一种编程语言,也许并不是为了面向对象编程而设计,但这并不意味着它不能实现面向对象编程所能实现的功能,就比如说,多态性。在本文中我们使用一个简单的单链表作为例子,展示 C 语言是如何体现多态性的。回页首结构体:不得不说的故事许多从写 C 代码开始,逐渐走向 C++ 的程序员都知道,其实 C++ 里面的 class,其前身正是 C 语言中的 structure。很多基于 C 语言背景介绍 C++ 的书籍,在介绍到 class 这一章的时候都会向读者清晰地展示,一个 C 语言里的 structure 是怎样逐渐变成一个典型的 C++ class 的,甚至最后得出结论:“structure 就是一个所有成员都公有的类”,当然了,class 还是 class,不能简单的把它看做一个复杂化了的 structure 而已。下面我们来看看在 C 语言中定义一个简单的存储整型数据的单链表节点是怎么做的,当然是用结构体。大部分人会像我一样,在 linkList.h 文件里定义: typedef struct Node* linkList;  struct Node                                       // 链表节点 {  int data;                                 // 存储的整型数据 linkList next;                            // 指向下一个链表节点 }; 链表有了,下面就是你想要实现的一些链表的功能,当然是定义成函数。我们只举几个常用功能: linkList initialLinklist();                               // 初始化链表 link newLinkList (int data);                        // 建立新节点 void insertFirst(linkList h,int data);              // 在已有链表的表头进行插入节点操作 void linkListOutput(linkList h);                           // 输出链表中数据到控制台这些都是再自然不过的 C 语言的编程过程,然后我们就可以在 linkList.c 文件中实现上述两个函数,继而在 main.c 中调用它们了。然而上面我们定义的链表还只能对整型数据进行操作。如果下次你要用到一个存储字符串类型的链表,就只好把上面的过程重新来过。也许你觉得这个在原有代码基础上做略微修改的过程并不复杂,可是也许我们会不断的增加对于链表这个数据结构的操作,而需要用链表来存储的数据类型也越来越多,这些都意味着海量的代码和繁琐的后期维护工作。当你有了上百个存储不同数据类型的链表结构,每当你要增加一个操作,或者修改某个操作的传入参数,工作量会变大到像一场灾难。但是我们可以改造上述代码,让它能够处理你所想要让它处理的任何数据类型:实行,字符型,乃至任何你自己定义的 structure 类型。回页首Void*:万能的指针“挂钩”几乎所有讲授 C 语言课程的老师都会告诉你:“指针是整个 C 语言的精髓所在。”而你也一直敬畏着指针,又爱又恨地使用着它。许多教材都告诉你,int * 叫做指向整型的指针,而 char * 是指向字符型的指针,等等不一而足。然而这里有一个另类的指针家族成员—— void *。不要按照通常的命名方式叫它做指向 void 类型的指针,它的正式的名字叫做:可以指向任意类型的指针。你一定注意到了“任意类型”这四个字,没错,实现多态,我们靠的就是它。下面来改造我们的链表代码,在 linkList.h 里,如下: typedef struct Node* linkList;  struct Node                                       // 链表节点 {  void *data;                               // 存储的数据指针 linkList next;                            // 指向下一个链表节点 };  linkList initialLinklist();                             // 初始化链表 link newLinkList (void *data);                    // 建立新节点 void insertFirst(linkList h, void *data);         // 在已有链表的表头进行插入节点操作 void linkListOutput(linkList h);                         // 输出链表中数据到控制台我们来看看现在这个链表和刚才那个只能存储整型数据的链表的区别。当你把 Node 结构体里面的成员定义为一个整型数据,就好像把这个链表节点打造成了一个大小形状固定的盒子,你定义一个链表节点,程序进行编译的时候编译器就为你打造一个这样的盒子:装一个 int 类型的数据,然后装一个 linkList 类型的指针。如果你想强行在这个盒子里装别的东西,编译器会告诉你,对不起,盒子的大小形状并不合适。所以你必须为了装各种各样类型的数据打造出不同的生产盒子的流水线,想要装哪种类型数据的盒子,就开启对应的流水线来生产。但是当你把结构体成员定义为 void *,一切都变得不同了。这时的链表节点不再像个大小形状固定的盒子,而更像一个挂钩,它可以挂上一个任意类型的数据。不管你需要存储什么类型的数据,你只要传递一个指针,把它存储到 Node 节点中去,就相当于把这个数据“挂”了上去,无论何时你都可以根据指针找到它。这时的链表仿佛变成了一排粘贴在墙上的衣帽钩,你可以挂一排大衣,可以挂一排帽子,可以挂一排围巾,甚至,你可以并排挂一件大衣一顶帽子一条围巾在墙上。void * 初露狰狞,多态离 C 语言并不遥远。回页首实现:你的多态你做主当你真正开始着手做这个工作的时候,你会发现把数据放入链表中的操作和普通的存放 int 类型的链表的实现并没有什么大的区别,很方便。但是当你要把已经存进去的数据读取出来的时候,就有一点麻烦了。对于 void * 类型的指针,编译器只知道它里面存储了一个地址,但是关于这个地址里的数据类型,编译器是没有任何概念的。毕竟我们不能指望编译器什么都知道,什么都能替你做好,所以存进去的数据的类型,作为程序员的我们必须清楚的知道,并且在取出这个数据的时候,用这一类型的指针来对 void * 做强制类型转换。为了方便的做到这一点,我采取的方法是在 Node 结构体中增加一个标识数据类型的域,并用一个枚举类型来存放这些数据类型。这时的 linkList.h 如下所示: #ifndef LINKLIST_H  #define LINKLIST_H  typedef struct Node* linkList;  enum dataType  {     INT,     DOUBLE,     CHAR,     STRING  };  struct Node                                               // 链表节点 {     void *data;                                       // 存储的数据指针    int dataType;                                     // 存储数据类型    linkList next;                                    // 指向下一个链表节点 };  linkList initialLinklist();                               // 初始化链表 linkList newLinkList (void *data, int dataType);          // 建立新节点 void insertFirst(linkList h, void *data, int dataType);   // 在已有链表的表头进行插入节点操作 void linkListOutput(linkList h);                          // 输出链表中数据到控制台 #endif 初始化链表,代码如下: linkList initialLinklist()  {  linkList link = (linkList*)malloc(sizeof(*link));     link->data = NULL;     link->dataType = -1;     link->next = NULL;     return link;  } 建立新节点,代码如下: linkList newLinkList (void *data, int dataType)  {     linkList link = (linkList*)malloc(sizeof(*link));     link->data = data;     link->dataType = dataType;     link->next = NULL;     return link;  } 在已有链表的表头进行插入节点操作,代码如下: void insertFirst(linkList h, void *data, int dataType)  {     linkList l = newLinkList(data, dataType);     l->next = h->next;     h->next = l;  } 输出链表中数据到控制台,代码如下: void linkListOutput(linkList h)  {     linkList p = h;     p = p->next;     while(p != NULL)     {       switch(p->dataType)     {        case 0:        {          printf("%4d", *(int*)(p->data));          break;        }        case 1:        {          printf("%4f", *(double*)(p->data));          break;        }        case 2:        {           printf("%4c", *(char*)(p->data));           break;        }        case 3:        {           printf("%s ", (char*)(p->data));           break;        }     }     p = p->next;   }     printf("/n");  } 回页首小结通过上面这个链表的小例子,大家可能已经看到了 C 语言的灵活性。这段代码虽然短并且功能简单,但是已经实现了多态性。这篇文章的本意并不在于想要告诉大家用 C 实现多态的方法,而多态的含义也无疑是更加广泛的。这篇文章的初衷其实是基于这样一点认识:面向对象是一种程序设计思想,而 C 语言则是一种编程语言。也许它并不是专门为了面向对象编程而设计,但是这绝不意味着它不能实现面向对象的程序设计。当然以上所展示的这几个操作,如果是用别的编程语言,可能只要寥寥几行就可以完成,但是 C 语言想告诉我们的是:也许我不擅长,但是并不意味着我做不到。作者:unbutun 发表于2011-6-28 20:35:00 原文链接阅读:17 评论:0 查看评论判断变量是全数字 test_num.sh  作者:unbutun 发表于2011-6-26 11:46:00 原文链接阅读:3 评论:0 查看评论做一次git的观众Git这个强大的版本管理系统,工作的时候默默注视着你的代码目录,所有的操作几乎都在.git目录中完成。今天我们来做一次git的观众,以便深入了解git的各个操作。 首先,新建一个目录:git-monitor,进入目录后,用下面的命令初始化一个git仓库:Bash代码  {1}gt; git init --bare git-monitor.git  然后,创建一个工作目录wp1,意思为working_copy_1,进入该目录,运行git init,以创建.git目录。 进入.git目录,会发现下列文件和目录:Bash代码  HEAD        config      description hooks/       info/        objects/     refs/  这些都是git的演员。我们当观众的,就从监控这些文件开始。但是演员分主角、配角,和跑龙套的,在这些文件中,config是配置文件,内容不会变的;hooks中的文件是一些回调程序的例子,删掉都没关系;description文件只为某些git的web应用提供描述信息。它们都是跑龙套的,剩下的文件和目录有:Bash代码  HEAD        info/        objects/       refs/  在后续的操作中,还有两位要上场,分别是index文件和logs目录,至此,主要演员表为:Bash代码  HEAD        index       info/        objects/       refs/        logs/  要用肉眼盯着它们看,实在不容易,于是我写了个ruby小程序(下载链接在最后),用于监控这些目录,一旦目录和文件有变化,就在控制台上向我们报告。我把这个程序放到.git目录下,并把它跑起来。接下来,好戏就开演了。 回到wp1目录。新建一个文件file1.txt,然后看看监控程序,发现没有任何输出,说明git对刚才的操作没有响应。既然没反应,那我就接着操作,在file1.txt中加一行内容File1.txt代码  content added by wp1, 1st time  再看看监控,还是没有反应。看来,只要我们不调用git命令,它就不会有反应。那我就调一个看看:Bash代码  {1}gt; git add .  再看看监控,终于有反应了:Bash代码  Created file: index  (in dir: git-monitor/wp1/.git)   Created file: c2/a04aa8cba9ba9a7a2fb8c9ecf74a3a0fc5e3fc  (in dir: git-monitor/wp1/.git/objects)  git add这个命令,根据`man git-add`的解释,是把某个文件加入到index。这个index实际上就是工作目录下文件和目录状态的一个快照(stage),每一次git提交所产生的tree对象,就是依据index文件产生的(对index同志的详细采访,可以参考[url=http://progit.org/book/zh/ch9-2.html]这里[/url])。 我们来看看产生的那个object到底是什么,根据git的规则,object的目录名加文件名,和起来是一个40字符的字符串,它是对文件内容进行SHA1 digest之后,用16进制编码得到的结果。此文件的内容是二进制的,要查看它,就要用下面的命令:Bash代码  {1}gt; git cat-file -t c2a04aa8cba9ba9a7a2fb8c9ecf74a3a0fc5e3fc   blob   {1}gt; git cat-file -p c2a04aa8cba9ba9a7a2fb8c9ecf74a3a0fc5e3fc   content added by wp1, 1st time  其中,-t这个参数是为了显示object类型,-p这个参数,是为了显示object的内容。显然,这个object就是刚才加进去的file1.txt,它是一个blob类型的对象,只存储文件内容和长度。 接下来,我把这次添加的内容提交一下(git commit -m 'commit by wp1, 1st time'),再看看监控,又有输出了,这次的内容还真丰富啊:Bash代码  Changed file: index   Created file: 16/71ae856c149673436da08f1ba026469c3a918d  (in dir: git-monitor/wp1/.git/objects)   Created file: 30/c64c3a55b02f4c251565ef057d402f84751b56  (in dir: git-monitor/wp1/.git/objects)   Created file: heads/master  (in dir: git-monitor/wp1/.git/refs)   Created file: HEAD  (in dir: git-monitor/wp1/.git/logs)   Created file: refs/heads/master  (in dir: git-monitor/wp1/.git/logs)  首先,我们发现index文件被改变了。但是,经过我仔细比对两次的index文件的二进制字节码后发现,它的内容并没有改变,所以可能是它的修改时间发生了改变。对此我想说的是:请高人指点! 再看后面新生成的两个文件,用我们上面的办法看看内容:Bash代码  {1}gt; git cat-file -p 1671ae856c149673436da08f1ba026469c3a918d   tree 30c64c3a55b02f4c251565ef057d402f84751b56   author Kevin Fu <corntrace@email.com> 1281230735 +0800  committer Kevin Fu <corntrace@email.com> 1281230735 +0800    commit by wp1, 1st time     {1}gt; git cat-file -p 30c64c3a55b02f4c251565ef057d402f84751b56   100644 blob c2a04aa8cba9ba9a7a2fb8c9ecf74a3a0fc5e3fc    file1.txt  显然,第一个文件是个commit对象,第二个文件是个tree对象,从引用关系来看,是先生成的tree对象,再生成的commit对象。注意,这个commit对象没有parent引用。 再看看后面生成的refs,用git show-refs可以查看所有refs的内容Bash代码  {1}gt; git show-refs   1671ae856c149673436da08f1ba026469c3a918d refs/heads/master  master当然指的是master分支,它的值指向刚才看到的commit对象 最后就是两个log文件。log文件虽然只是供人查看,但在git中的地位非同一般。先看看其内容:Bash代码  $ cat logs/HEAD   0000000000000000000000000000000000000000 1671ae856c149673436da08f1ba026469c3a918d Kevin Fu <corntrace@email.com> 1281230735 +0800 commit (initial): commit by wp1, 1st time     $ cat logs/refs/heads/master   0000000000000000000000000000000000000000 1671ae856c149673436da08f1ba026469c3a918d Kevin Fu <corntrace@email.com> 1281230735 +0800 commit (initial): commit by wp1, 1st time  可以看到,这两个文件的内容目前是一样的。这个文件记录了什么呢?它记录的是工作目录的状态变化。其中,那一串0表示,一起是从零开始的,因为git在初始化工作目录时,并没有创建任何对象,要表示初始状态,只好用40个0来表示了。后面那串,就是指向刚才的本次的commit对象。这条记录解释出来,就是:在1281230735 +0800时刻,由Kevin Fu做了一次提交,工作目录从初始状态,转到commit 1671ae8对应的状态。 这个时候,用过git的人可能会生疑问:用git log看到的输出不是这个样子的呀。说对了,git log的输出,并不是由这里的log文件产生的,我认为,它的内容是根据commit之间的关联关系,实时计算并显示出来的。而这里的logs,是git中的reflog,用git reflog命令可以查看:Bash代码  {1}gt; git reflog   1671ae8 HEAD@{0}: commit (initial): commit by wp1, 1st time  这个内容,就跟上面的对应上了吧。为什么说这个log文件很重要呢?因为在git中,objects其实都是死的,绝大多数情况下,一旦创建就不会被修改,也不会被删除;当版本控制的内容发生变化时,只有新的objects被创建出来,没有旧的objects被改变。那么一堆死东西,如何实现灵活的版本变化呢?第一个就是靠不断变化的版本指针,比如HEAD以及refs/heads/master文件,第二个,就是靠记录工作目录变化情况的日志文件。有了日志文件,你想查看谁就查看谁,想往哪个版本跳就往哪个版本跳,想合并谁就合并谁。许多git命令,都是基于这个思想而设计的。 接下来,我再添加一个文件file2.txt,但不是在master branch中,而是新开一个branch: advanced,我们看看开分支的时候,监控有何变化:Bash代码  {1}gt; git checkout -b advanced   (monitor outputs)   Changed file: index   Changed file: HEAD   Created file: heads/advanced  (in dir: /Users/corntrace/git-monitor/wp1/.git/refs)   Created file: refs/heads/advanced  (in dir: /Users/corntrace/git-monitor/wp1/.git/logs)   Changed file: HEAD  (in dir: /Users/corntrace/git-monitor/wp1/.git/logs)  除去那个index文件的变化,看看其他的文件。首先是HEAD文件,其内容变成了:refs/heads/advanced,说明HEAD已经移到了advanced分支上。再看看新产生的refs/heads/advanced文件:Bash代码  {1}gt; git show-ref   1671ae856c149673436da08f1ba026469c3a918d refs/heads/advanced   1671ae856c149673436da08f1ba026469c3a918d refs/heads/master  可见目前它与master分支指向同一个commit。再来看看两个日志文件:Bash代码  $ cat logs/HEAD   0000000000000000000000000000000000000000 1671ae856c149673436da08f1ba026469c3a918d Kevin Fu <corntrace@email.com> 1281230735 +0800 commit (initial): commit by wp1, 1st time   1671ae856c149673436da08f1ba026469c3a918d 1671ae856c149673436da08f1ba026469c3a918d Kevin Fu <corntrace@email.com> 1281236136 +0800 checkout: moving from master to advanced  它果然把我的一举一动都记录下来了。在第二条记录里,两个sha1值是一样的,说明没有提交,只有指针的创建或改变。 再看看另一个log:Bash代码 0000000000000000000000000000000000000000 1671ae856c149673436da08f1ba026469c3a918d Kevin Fu <corntrace@email.com> 1281236136 +0800 branch: Created from HEAD  它说明了advanced分支是从零开始,转到commit 671ae8的。看来它一点都不含糊啊。 现在,我增加文件file2.txt,并添加以下内容,但分两次提交:第一次提交前两行,第二次提交后两行File2.txt代码content added by wp1, 1st time   additional content added by wp1, 1st time too     # TODO: implement a feature   # I plan to do ...  显然,第一次提交将会产生3个objects:一个commit对象,一个file2.txt的blob对象,还有一个tree对象,另外,refs/heads/advanced会指向目前的这个commit对象,然后两个log文件(logs/HEAD和logs/refs/heads/advanced)会添加一些内容,我全部列在这里:Bash代码{1}gt; git show-ref   35ba29ec8f31d5372b75d7be6b1ec7f03c0fb666 refs/heads/advanced   1671ae856c149673436da08f1ba026469c3a918d refs/heads/master     {1}gt; git cat-file -p 35ba29ec8f31d5372b75d7be6b1ec7f03c0fb666   tree 9c1c4549a869ede4d5f85c93594c1c23c311122f   parent 1671ae856c149673436da08f1ba026469c3a918d   author Kevin Fu <corntrace@email.com> 1281251100 +0800  committer Kevin Fu <corntrace@email.com> 1281251100 +0800    commit by wp1 for file2.txt, 1st time     {1}gt; git cat-file -p 9c1c4549a869ede4d5f85c93594c1c23c311122f    100644 blob c2a04aa8cba9ba9a7a2fb8c9ecf74a3a0fc5e3fc    file1.txt   100644 blob baa4a1630ce88a9198b5eda885884aadab795806    file2.txt     {1}gt; git cat-file -p baa4a1630ce88a9198b5eda885884aadab795806    content added by wp1, 1st time   additional content added by wp1, 1st time     {1}gt; cat logs/HEAD   0000000000000000000000000000000000000000 1671ae856c149673436da08f1ba026469c3a918d Kevin Fu <corntrace@email.com> 1281230735 +0800 commit (initial): commit by wp1, 1st time   1671ae856c149673436da08f1ba026469c3a918d 1671ae856c149673436da08f1ba026469c3a918d Kevin Fu <corntrace@email.com> 1281236136 +0800 checkout: moving from master to advanced   1671ae856c149673436da08f1ba026469c3a918d 35ba29ec8f31d5372b75d7be6b1ec7f03c0fb666 Kevin Fu <corntrace@email.com> 1281251100 +0800 commit: commit by wp1 for file2.txt, 1st time     {1}gt; cat logs/refs/heads/advanced   0000000000000000000000000000000000000000 1671ae856c149673436da08f1ba026469c3a918d Kevin Fu <corntrace@email.com> 1281236136 +0800 branch: Created from HEAD   1671ae856c149673436da08f1ba026469c3a918d 35ba29ec8f31d5372b75d7be6b1ec7f03c0fb666 Kevin Fu <corntrace@email.com> 1281251100 +0800 commit: commit by wp1 for file2.txt, 1st time  可以看到,这次的commit对象中多了parent的引用,就是指向上一次的commit。 接下来我提交第二部分的内容,其结果与刚才的分析相似,就不写了。提交之后,我转回master分支,将advanced分支中的内容合并进来,然后将master分支推送出去,我就可以下班了。 我们看看转回master分支时,监控都有哪些输出:Bash代码Changed file: index   Changed file: HEAD   Changed file: HEAD  (in dir: /Users/corntrace/git-monitor/wp1/.git/logs)  可见,除了那个index文件,HEAD文件的内容被该为:ref: refs/heads/master,logs/HEAD文件中添加了一行记录分支跳转的日志。 接下来我运行git merge advanced。合并完成后,看看监控的输出:Bash代码Changed file: index   Changed file: heads/master  (in dir: /Users/corntrace/git-monitor/wp1/.git/refs)   Changed file: HEAD  (in dir: /Users/corntrace/git-monitor/wp1/.git/logs)   Changed file: refs/heads/master  (in dir: /Users/corntrace/git-monitor/wp1/.git/logs)  这里列出后三项的内容:Bash代码{1}gt; git show-ref   8227ae64f1f651669c6445d4c37909c8443df209 refs/heads/advanced   8227ae64f1f651669c6445d4c37909c8443df209 refs/heads/master     {1}gt; cat logs/HEAD   (猜都能猜出来,就省了吧)     {1}gt; cat logs/refs/heads/master   0000000000000000000000000000000000000000 1671ae856c149673436da08f1ba026469c3a918d Kevin Fu <corntrace@email.com> 1281230735 +0800 commit (initial): commit by wp1, 1st time   1671ae856c149673436da08f1ba026469c3a918d 8227ae64f1f651669c6445d4c37909c8443df209 Kevin Fu <corntrace@email.com> 1281252267 +0800 merge advanced: Fast-forward  从分支的指向可以看到,它指到advanced分支对应的commit上去了;从logs的内容可以看到,本次的操作称为merge advanced: Fast-forword。除此之外,并没有产生任何object,连commit都没有,这是因为在合并之后,git分析出当前的目录树结构与advanced分支中的目录树是一样的,所以只是简单的把master的指针指向advanced分支。 OK,在下班之前,我还是要看看master分支中的代码,确保没有什么坏代码被交上去。但是,我的神,file2.txt中有一个TODO,这要是被老板看见了,还不要我晚上加班啊!我能把它删掉再提交吗?不能啊,老板要是往回看一个版本,不就找出来了!因此,当务之急就是,把master的版本指针退回去,让它指向原来的1671ae8这个版本。git reset这个命令可以帮到我。Bash代码{1}gt; git reset --hard 1671ae8   (monitor outputs)   Changed file: index   Changed file: heads/master  (in dir: /Users/corntrace/git-monitor/wp1/.git/refs)   Changed file: HEAD  (in dir: /Users/corntrace/git-monitor/wp1/.git/logs)   Changed file: refs/heads/master  (in dir: /Users/corntrace/git-monitor/wp1/.git/logs)     {1}gt; git show-ref   8227ae64f1f651669c6445d4c37909c8443df209 refs/heads/advanced   1671ae856c149673436da08f1ba026469c3a918d refs/heads/master     {1}gt; cat logs/refs/heads/master   0000000000000000000000000000000000000000 1671ae856c149673436da08f1ba026469c3a918d Kevin Fu <corntrace@email.com> 1281230735 +0800 commit (initial): commit by wp1, 1st time   1671ae856c149673436da08f1ba026469c3a918d 8227ae64f1f651669c6445d4c37909c8443df209 Kevin Fu <corntrace@email.com> 1281252267 +0800 merge advanced: Fast-forward   8227ae64f1f651669c6445d4c37909c8443df209 1671ae856c149673436da08f1ba026469c3a918d Kevin Fu <corntrace@email.com> 1281253859 +0800 1671ae8: updating HEAD  可以看到,master的head是真的退回去了,reflog中的记录,稍候再说。现在只要把advanced中想要的那个提交弄过来,就万事大吉了。git cherry-pick这个命令这是用来做这个的。Bash代码{1}gt; git cherry-pick --ff 35ba29e   (此时这里居然没有任何输出,git的作者们太高估用户了!来看看monitor outputs)   Changed file: index   Changed file: heads/master  (in dir: /Users/corntrace/git-monitor/wp1/.git/refs)   Changed file: HEAD  (in dir: /Users/corntrace/git-monitor/wp1/.git/logs)   Changed file: refs/heads/master  (in dir: /Users/corntrace/git-monitor/wp1/.git/logs)     {1}gt; git show-ref   8227ae64f1f651669c6445d4c37909c8443df209 refs/heads/advanced   35ba29ec8f31d5372b75d7be6b1ec7f03c0fb666 refs/heads/master     {1}gt; cat file2.txt   content added by wp1, 1st time   additional content added by wp1, 1st time  看,TODO果然是没有了!在这里我用的--ff参数,是要告诉git,只需要做fast-forword就可以了,因为35ba29e这个commit的parent,正是先前的master的head(1671ae8)。 OK,我可以把master分支推送到远程仓库里去了。在推之前,我需要把远程仓库加进来。这里我就用一个本地的仓库来代替远程的吧。Bash代码{1}gt; git remote add origin /path/to/git-monitor.git   (这一步monitor无输出)     {1}gt; git push origin master   (monitor outputs)   Created file: remotes/origin/master  (in dir: /Users/corntrace/git-monitor/wp1/.git/refs)   Created file: refs/remotes/origin/master  (in dir: /Users/corntrace/git-monitor/wp1/.git/logs)     {1}gt; git show-ref   8227ae64f1f651669c6445d4c37909c8443df209 refs/heads/advanced   35ba29ec8f31d5372b75d7be6b1ec7f03c0fb666 refs/heads/master   35ba29ec8f31d5372b75d7be6b1ec7f03c0fb666 refs/remotes/origin/master     {1}gt; cat logs/refs/remotes/origin/master   0000000000000000000000000000000000000000 35ba29ec8f31d5372b75d7be6b1ec7f03c0fb666 Kevin Fu <corntrace@email.com> 1281255166 +0800 update by push  可见,这一步的操作就是新建了一个remote master分支的ref,然后添加了一个logs文件。看来所有的事情都办妥了,那就下班吧。 但是老板还没有下班,而是正准备做code review。他把代码checkout下来:Bash代码{1}gt; git co /path/to/git-monitor.git && cd git-monitor   {1}gt; git reflog   35ba29e HEAD@{0}: clone: from /Users/corntrace/git-monitor/git-monitor.git  他的reflog文件中,完全没有我刚才操作的内容。看来,git是不会提交reflog到仓库中的。作者:unbutun 发表于2011-6-24 19:52:00 原文链接阅读:34 评论:0 查看评论用CodeViz绘制静态函数调用关系图首先要根据README里编译生成一个gcc的patch版本用这个patch过的gcc就能生成.cdepn中间文件,然后再用genfull和gengraph就能生成静态函数调用关系图了genfull -g cdepn -o full.graphgengraph -t --output-type png -f main我现在遇到个程序只能生成个main,具体原因不明,这时候只能用替代的方法genfull -g cobjdump了作者:unbutun 发表于2011-6-24 0:05:00 原文链接阅读:12 评论:0 查看评论公司简介|广告服务|银行汇款帐号|联系方式|版权声明|法律顾问|问题报告北京创新乐知信息技术有限公司 版权所有, 京 ICP 证 070598 号世纪乐知(北京)网络技术有限公司 提供技术支持Copyright © 1999-2011, CSDN.NET, All Rights Reserved