简体   繁体   English

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

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

Second try 第二次尝试

Ok, maybe my first attempt to ask the question was too confuse. 好吧,也许我第一次尝试提问这个问题太混乱了。 So, here we go again... 所以,我们再来一次......

In a function that takes a variable number of arguments, like printf , when you pass a struct what is the kind of code generated you should expected? 在一个带有可变数量参数的函数中,比如printf ,当你传递一个struct时,你应该期望生成什么样的代码?

I'm asking this, based on this code: 基于这段代码,我问这个问题:

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

When compiling this on my windows box, it pass 2 integer arguments: 在我的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

But the code generated by my linux box, just pass one memory address to it: 但是我的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

So this is my question, when you pass only one struct to a function like printf , the code generated should pass just one argument or push all struct members to it? 所以这是我的问题,当你只将一个结构传递给像printf这样的printf ,生成的代码应该只传递一个参数或将所有结构成员推送给它?

First try 第一次尝试

A friend of mine was having some problems with a program that wasn't working as expected on Windows. 我的一个朋友在Windows上没有按预期工作的程序遇到一些问题。 So I took a look at the source code and trimmed down the test case to this: 所以我看一下源代码并将测试用例修改为:

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

So tested on a Windows 7 box with 所以在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.

And the output is this: 输出是这样的:

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

On a linux box with 在一个带有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.

I got this output: 我得到了这个输出:

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

The output from linux box one, is the one that i expected... linux box one的输出是我预期的...

Then, I took a look at the assembly code generated by each compiler and saw that on my Windows box, the code was passing the whole struct to printf, both v1 and v2 members (and this justifies the results). 然后,我查看了每个编译器生成的汇编代码,并在我的Windows框中看到代码将整个结构传递给printf,v1和v2成员(这证明了结果)。 But on the Linux box, the code generated passed just the first member of the struct, as I expected. 但是在Linux机器上,生成的代码只传递了结构的第一个成员,正如我所期望的那样。

So what should be the behavior in this case? 那么在这种情况下应该是什么行为呢?

For me, the expected one is the Linux one, but maybe I'm wrong and this is a case of undefined behavior. 对我来说,预期的一个是Linux,但也许我错了,这是一个未定义的行为。

The expected behavior is undefined behavior . 预期的行为是未定义的行为 The C language requires that you pass arguments of the correct type (matching the format string) to printf , and there is no format string which can match struct types. C语言要求您将正确类型的参数(与格式字符串匹配)传递给printf ,并且没有可以匹配struct类型的格式字符串。

You may be able to get the C implementation you're using to print meaningful data by passing structures this way, but it's non-portable, dependent upon calling convention, and unreliable. 您可以通过以这种方式传递结构来获取用于打印有意义数据的C实现,但它不可移植,取决于调用约定,并且不可靠。 Don't do it. 不要这样做。 Just pass the individual members you want to print. 只需传递您要打印的个人成员即可。

Look, printf is just a simple function. 看, printf只是一个简单的功能。 It evaluates its arguments from right-to-left and just pushes them on the stack. 它从右到左评估它的参数,并将它们推到堆栈上。 It does no type checking. 没有类型检查。

Then the format string expects to see certain things, namely ints, doubles, or character pointers. 然后格式字符串期望看到某些内容,即整数,双精度或字符指针。 Nothing else. 没有其他的。

So just pass it what it expects. 所以只需按照预期传递它。

If the compiler doesn't have specific information about how the argument should be passed, such as when there's no prototype or for arguments that are passed where the protoype have an ellipsis ( '...' ), the compiler follows certain rules for passing the arguments: 如果编译器没有关于如何传递参数的特定信息,例如当没有原型或者在原型具有省略号( '...' )的情况下传递的参数时,编译器遵循某些传递规则论点:

  • the integer promotions are applied (note this doesn't apply to structures) 应用整数促销(注意这不适用于结构)
  • if the argument has float type it's promoted to double 如果参数有float类型,它会被提升为double

After these default argument promotions are applied, the argument is simply copied to wherever the compiler normally copies arguments (generally, the stack). 应用这些默认参数提升后,只需将参数复制到编译器通常复制参数(通常是堆栈)的任何位置。 So a struct argument would be copied to the stack. 因此,struct参数将被复制到堆栈中。

Try examining the assembly output for the following code, and you'll see that GCC will copy the structures passed to foo() to the stack: 尝试检查以下代码的程序集输出,您将看到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 might not be applying this rule to your printf() test because it has special knowledge of printf() and knows that only certain types are supported by the format string. GCC可能没有将此规则应用于printf()测试,因为它具有printf()特殊知识,并且知道格式字符串仅支持某些类型。 If an argument type doesn't match what's expected by a format specifier then undefined behavior occurs (so the compiler doesn't have to do what you might expect otherwise). 如果参数类型与格式说明符的预期不匹配,则会发生未定义的行为(因此编译器不必执行您可能期望的操作)。 I'm surprised you don't get some sort of warning. 我很惊讶你没有得到某种警告。 If you leave out the #include <stddio.h> , you'll probably see a warning something like, "warning: incompatible implicit declaration of built-in function 'printf'", which hints at GCC's special treatment of printf() . 如果省略#include <stddio.h> ,你可能会看到类似“警告:内置函数'printf'的不兼容隐式声明”的警告,这暗示了GCC对printf()的特殊处理。

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

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