繁体   English   中英

在C中,将struct作为参数传递给printf的预期行为是什么?

[英]In C, what is the expected behavior of passing a struct as an argument to printf?

第二次尝试

好吧,也许我第一次尝试提问这个问题太混乱了。 所以,我们再来一次......

在一个带有可变数量参数的函数中,比如printf ,当你传递一个struct时,你应该期望生成什么样的代码?

基于这段代码,我问这个问题:

#include <stdio.h>

struct edge{
    int v1, v2;
};

int main(void){
    struct edge edges;

    edges.v1=5;
    edges.v2=20;

    printf("'%d'\n",  edges);

return 0;
}

在我的Windows框中编译时,它传递2个整数参数:

    .file   "simple_test_case2.c"
    .def    ___main;    .scl    2;  .type   32; .endef
    .section .rdata,"dr"
LC0:
    .ascii "'%d'\12\0"
    .text
.globl _main
    .def    _main;  .scl    2;  .type   32; .endef
_main:
    pushl   %ebp
    movl    %esp, %ebp
    andl    $-16, %esp
    subl    $32, %esp
    call    ___main
    movl    $5, 24(%esp)
    movl    $20, 28(%esp)
    movl    24(%esp), %eax
    movl    28(%esp), %edx
    movl    %eax, 4(%esp)
    movl    %edx, 8(%esp)
    movl    $LC0, (%esp)
    call    _printf
    movl    $0, %eax
    leave
    ret
    .def    _printf;    .scl    2;  .type   32; .endef

但是我的linux框生成的代码只是将一个内存地址传递给它:

    .file   "simple_test_case2.c"
    .section    .rodata
.LC0:
    .string "'%d'\n"
    .text
    .align 2
.globl main
    .type   main, @function
main:
.LFB2:
    pushq   %rbp
.LCFI0:
    movq    %rsp, %rbp
.LCFI1:
    subq    $16, %rsp
.LCFI2:
    movl    $5, -16(%rbp)
    movl    $20, -12(%rbp)
    movq    -16(%rbp), %rsi
    movl    $.LC0, %edi
    movl    $0, %eax
    call    printf
    movl    $0, %eax
    leave
    ret
.LFE2:
    .size   main, .-main
.globl __gxx_personality_v0
    .section    .eh_frame,"a",@progbits
.Lframe1:
    .long   .LECIE1-.LSCIE1
.LSCIE1:
    .long   0x0
    .byte   0x1
    .string "zPR"
    .uleb128 0x1
    .sleb128 -8
    .byte   0x10
    .uleb128 0x6
    .byte   0x3
    .long   __gxx_personality_v0
    .byte   0x3
    .byte   0xc
    .uleb128 0x7
    .uleb128 0x8
    .byte   0x90
    .uleb128 0x1
    .align 8
.LECIE1:
.LSFDE1:
    .long   .LEFDE1-.LASFDE1
.LASFDE1:
    .long   .LASFDE1-.Lframe1
    .long   .LFB2
    .long   .LFE2-.LFB2
    .uleb128 0x0
    .byte   0x4
    .long   .LCFI0-.LFB2
    .byte   0xe
    .uleb128 0x10
    .byte   0x86
    .uleb128 0x2
    .byte   0x4
    .long   .LCFI1-.LCFI0
    .byte   0xd
    .uleb128 0x6
    .align 8
.LEFDE1:
    .ident  "GCC: (GNU) 4.1.2 20080704 (Red Hat 4.1.2-48)"
    .section    .note.GNU-stack,"",@progbits

所以这是我的问题,当你只将一个结构传递给像printf这样的printf ,生成的代码应该只传递一个参数或将所有结构成员推送给它?

第一次尝试

我的一个朋友在Windows上没有按预期工作的程序遇到一些问题。 所以我看一下源代码并将测试用例修改为:

#include <stdio.h>

struct edge{
    int v1, v2;
};

int main(void){
    struct edge edges;

    edges.v1=5;
    edges.v2=20;

    //This is the expected behavior for me:
    printf("'%d' '%d' '%d' '%d'\n",  edges.v1,  edges.v1,  edges.v1,  edges.v1);

    //This is supposed to work like this? It should pass the whole struct to printf?
    printf("'%d' '%d' '%d' '%d'\n",  edges,  edges,  edges,  edges);
    printf("'%d' '%d' '%d' '%d'\n",  edges,  edges);

    return 0;
}

所以在Windows 7的盒子上测试过

gcc (tdm-1) 4.5.0
Copyright (C) 2010 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

输出是这样的:

'5' '5' '5' '5'
'5' '20' '5' '20'
'5' '20' '5' '20'

在一个带有Linux的盒子上

gcc (GCC) 4.1.2 20080704 (Red Hat 4.1.2-48)
Copyright (C) 2006 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

我得到了这个输出:

'5' '5' '5' '5'
'5' '5' '5' '5'
'5' '5' '229971840' '-641776368'

linux box one的输出是我预期的...

然后,我查看了每个编译器生成的汇编代码,并在我的Windows框中看到代码将整个结构传递给printf,v1和v2成员(这证明了结果)。 但是在Linux机器上,生成的代码只传递了结构的第一个成员,正如我所期望的那样。

那么在这种情况下应该是什么行为呢?

对我来说,预期的一个是Linux,但也许我错了,这是一个未定义的行为。

预期的行为是未定义的行为 C语言要求您将正确类型的参数(与格式字符串匹配)传递给printf ,并且没有可以匹配struct类型的格式字符串。

您可以通过以这种方式传递结构来获取用于打印有意义数据的C实现,但它不可移植,取决于调用约定,并且不可靠。 不要这样做。 只需传递您要打印的个人成员即可。

看, printf只是一个简单的功能。 它从右到左评估它的参数,并将它们推到堆栈上。 没有类型检查。

然后格式字符串期望看到某些内容,即整数,双精度或字符指针。 没有其他的。

所以只需按照预期传递它。

如果编译器没有关于如何传递参数的特定信息,例如当没有原型或者在原型具有省略号( '...' )的情况下传递的参数时,编译器遵循某些传递规则论点:

  • 应用整数促销(注意这不适用于结构)
  • 如果参数有float类型,它会被提升为double

应用这些默认参数提升后,只需将参数复制到编译器通常复制参数(通常是堆栈)的任何位置。 因此,struct参数将被复制到堆栈中。

尝试检查以下代码的程序集输出,您将看到GCC将传递给foo()的结构复制到堆栈:

#include <stdarg.h>

void foo( size_t siz, ...);

struct x
{
    int x;
    int y;
};

struct y
{
    char a;
};

struct z
{
    double x;
    double y;
    int z;
};

int main()
{
    struct x x1;
    struct y y1;
    struct z z1;

    foo( sizeof(x1), x1);
    foo( sizeof(y1), y1);
    foo( sizeof(z1), z1);

    return 0;
}

GCC可能没有将此规则应用于printf()测试,因为它具有printf()特殊知识,并且知道格式字符串仅支持某些类型。 如果参数类型与格式说明符的预期不匹配,则会发生未定义的行为(因此编译器不必执行您可能期望的操作)。 我很惊讶你没有得到某种警告。 如果省略#include <stddio.h> ,你可能会看到类似“警告:内置函数'printf'的不兼容隐式声明”的警告,这暗示了GCC对printf()的特殊处理。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM