不同编译器对命令行解析的不同段错误

时间:2014-09-04 08:08:56

标签: c gcc segmentation-fault cygwin mingw

我通过Zed Shaw的教程学习C并在this exercise遇到了一些问题。代码如下

#include <stdio.h>

int main(int argc, char *argv[])
{
    int i = 0;
    while(i < argc) {
        printf("arg %d: %s\n", i, argv[i]);
        i++;
    }
    // section removed for brevity
    return 0;
}

我使用Windows并且不喜欢虚拟盒的麻烦所以我一直在Cygwin中运行。我有两个编译器,一个是Cygwin附带的gcc,另一个是mingw附带的版本(gcc),所以我可以使用DrMemory。

我制作文件(名为ex11.c)就像这样

# Makefile
ex11: ex11.c
    gcc -o ex11.exe ex11.c
    i686-pc-mingw32-gcc.exe -static-libgcc -static-libstdc++ -ggdb -o ex11b.exe ex11.c

# Command Line
>>> make ex11
   ...
   etc

我得到的第二个命令here

$ gcc --version
gcc.exe (rubenvb-4.6.3) 4.6.3)
$ i686-pc-mingw32-gcc --version
i686-pc-mingw32-gcc (GCC) 4.7.3

然后当我运行它们(./ex11./ex11b)时,我会遇到问题。在没有命令行参数的情况下运行普通版本(没有b)会给我一个段错误。使用参数运行给出了这个输出:

$ ./ex11 a
arg 0: a
arg 1: a

运行mingw版本(带b)我没有命令行参数没有问题:

$ ./ex11b
arg 0: (null)

但是然后使用命令行参数($ ./ex11b a)运行相同的段错误。

第一个

的汇编输出
    .file   "ex11.c"
    .def    __main; .scl    2;  .type   32; .endef
    .section .rdata,"dr"
.LC0:
    .ascii "arg %d: %s\12\0"
    .text
    .globl  main
    .def    main;   .scl    2;  .type   32; .endef
    .seh_proc   main
main:
    pushq   %rbp
    .seh_pushreg    %rbp
    movq    %rsp, %rbp
    subq    $48, %rsp
    .seh_stackalloc 48
    .seh_setframe   %rbp, 48
    .seh_endprologue
    movl    %ecx, 16(%rbp)
    movq    %rdx, 24(%rbp)
    call    __main
    movl    $0, -4(%rbp)
    jmp .L2
.L3:
    movq    24(%rbp), %rax
    addq    $72, %rax
    movq    (%rax), %rcx
    leaq    .LC0(%rip), %rax
    movl    -4(%rbp), %edx
    movq    %rcx, %r8
    movq    %rax, %rcx
    call    printf
    addl    $1, -4(%rbp)
.L2:
    movl    -4(%rbp), %eax
    cmpl    16(%rbp), %eax
    jl  .L3
    movl    $0, %eax
    addq    $48, %rsp
    popq    %rbp
    ret
    .seh_endproc
    .def    printf; .scl    2;  .type   32; .endef

第二个

的汇编输出
    .file   "ex11.c"
    .def    ___main;    .scl    2;  .type   32; .endef
    .section .rdata,"dr"
LC0:
    .ascii "arg %d: %s\12\0"
    .text
    .globl  _main
    .def    _main;  .scl    2;  .type   32; .endef
_main:
LFB6:
    .cfi_startproc
    pushl   %ebp
    .cfi_def_cfa_offset 8
    .cfi_offset 5, -8
    movl    %esp, %ebp
    .cfi_def_cfa_register 5
    andl    $-16, %esp
    subl    $32, %esp
    call    ___main
    movl    $0, 28(%esp)
    jmp L2
L3:
    movl    12(%ebp), %eax
    addl    $36, %eax
    movl    (%eax), %eax
    movl    %eax, 8(%esp)
    movl    28(%esp), %eax
    movl    %eax, 4(%esp)
    movl    $LC0, (%esp)
    call    _printf
    addl    $1, 28(%esp)
L2:
    movl    28(%esp), %eax
    cmpl    8(%ebp), %eax
    jl  L3
    movl    $0, %eax
    leave
    .cfi_restore 5
    .cfi_def_cfa 4, 4
    ret
    .cfi_endproc
LFE6:
    .def    _printf;    .scl    2;  .type   32; .endef

我想知道导致段错误的原因。我将i初始化为0,因此我有时会尝试获取一个空值,这是一个不值得的。我想知道的是,这些编译器的不同之处在于他们会像这样打破。

我也很好奇我如何重写这个,所以我可以从i=0开始

2 个答案:

答案 0 :(得分:4)

您的编译器或环境似乎以某种方式被破坏了。 argv[]数组的所有元素都必须指向字符串,并且只有argv[argc]必须为NULL。如果程序名称不可用,则argv[0]必须指向空字符串("")。

可以做的是在你的循环中测试NULL,但你真的不应该:

while(i < argc) {
    if (argv[i]) {
        printf("arg %d: %s\n", i, argv[i]);
    }
    i++;
}

答案 1 :(得分:1)

您的编译器似乎生成了错误的地址,特别是argv的数组偏移量的计算,如32位(i686)汇编代码中所示:

L3:
    movl    12(%ebp), %eax
    addl    $36, %eax
    movl    (%eax), %eax
    movl    %eax, 8(%esp) 
    movl    28(%esp), %eax 
    movl    %eax, 4(%esp)
...
call _printf

解决这个烂摊子,你最终会得到以下结果:

printf(..., i, argv[9]); //argv[9]

addl $1, 28(%esp)指令是C代码中的i++,因此您可以猜出{C}代码中12(%ebp)28(%esp)的等价物。

无论如何,大局是这样的:argv[i]没有做任何事情,因为传递给argv[9]的{​​{1}}始终是printfaddl $36, %eax应为addl %edx, %eax,假设在执行添加操作之前%edx寄存器用于存储i的值,它不在程序集中你提供的代码。

换句话说,您的代码无法使用任何编译器正确编译。不幸的是我不知道是什么导致了这个问题。您是否尝试在Cygwin shell之外使用i686-pc-mingw32-gcc.exe?也许你的Cygwin安装有些搞砸了?