论坛元老 
   
	- 积分
 - 3371
 
        - 金钱
 - 3371 
 
       - 注册时间
 - 2015-1-7
 
      - 在线时间
 - 794 小时
 
 
 
 | 
 
 本帖最后由 ianhom 于 2016-8-10 22:38 编辑  
 
本人学艺不精,一直以来都认为C语言中函数只能返回结构体指针,而不能返回结构体实体,今天出于好奇试验了一下,在本人的编译环境下(IAR for ARM 6.7)果然可以,就把试验过程记录下来分享给大家。 
 
首先是对函数返回结构体的编译结果 
[mw_shl_code=c,true] 
typedef struct _T 
{ 
    uint8 u8a; 
    uint8 u8b; 
}T_STRUCTURE; 
 
T_STRUCTURE g_tB; 
 
T_STRUCTURE test1(void) 
{ 
    T_STRUCTURE tC; 
    tC.u8a = 5; 
    tC.u8b = 6; 
    return tC; 
} 
 
int main (void) 
{ 
    g_tB = test1();    /* 返回值为结构体实体 */ 
    printf("g_tB.u8a = %d\n", g_tB.u8a); 
    return 0; 
}[/mw_shl_code] 
编译无告警或错误。T_STRUCTURE是一个结构体类型,在这里用T_STRUCTURE定义了全局结构体g_tB,test1()是一个返回值为T_STRUCTURE结构体实体的函数,其内容为创建一个临时结构体实体tC,对tC的成员赋值后直接返回tC。main函数中g_tB = test1()来获取test1()的返回值并赋值给g_tB。写到这里相信很多朋友都有一个疑问,就是这个tC存在在栈上,直接返回给调用者不是会出问题吗。而实际上通过汇编发现,这里返回的就是结构体实体成员的“值”,而不是结构体的“指针”,这些值随后就赋值给g_tB,所以这样使用没有问题。 
来看一下汇编程序: 
1 
 
 
2 
 
 
(1)test1()函数体内在栈上创建了一个结构体tC,并分别为其两个成员赋值数值5和6 
(2)当执行到return tC的之后,将整个tC结构体的值赋值到R0上,然后退出函数 
(3)退出函数后,将g_tB的地址赋值给R1,然后将R0中代表tC结构体的值赋值给R1中所指向的g_tB,实现整个结构体实体的返回。 
(注:其实一般返回值也通过R0返回,不过因为为了简化试验,试验中的结构体成员只有2个字节,当结构体成员size比较多时,编译结果会复杂一些,有兴趣的朋友可以试一下结构体size为8字节和20字节时编译的汇编程序) 
 
以上返回的结构体实体是存在于栈中,同时也试验将全局结构体g_tA的值返回给g_tB,也是同样的方法通过R0将值返回给g_tB。 
 
再回到我们之前的做法,如果一个结构体的内部成员的值想通过调用某个函数被修改,通常是把该结构体指针作为入参来调用该函数。 
[mw_shl_code=c,true] 
typedef struct _T 
{ 
    uint8 u8a; 
    uint8 u8b; 
}T_STRUCTURE; 
 
T_STRUCTURE g_tB; 
 
void test3(T_STRUCTURE *ptD) 
{ 
    ptD->u8a = 3; 
    ptD->u8b = 4; 
    return; 
} 
 
int main (void) 
{    
    test3(&g_tB);      /* 通过结构体指针作为入参修改结构体值 */ 
    printf("g_tB.u8a = %d\n", g_tB.u8a); 
    return 0; 
} 
[/mw_shl_code] 
上面的代码是最熟悉的方式,test3()是一个无返回值,入参为T_STRUCTURE结构体指针的函数,函数内容为给结构体成员赋值数组3和4,然后直接返回。来看下汇编程序: 
 
 
 
 
(1)调用test3()之前,将g_tB的地址赋值给R0,作为参数传入函数 
(2)在函数中直接对R0中的g_tB成员进行赋值3,4 
(3)直接返回。 
在试验的编译环境下,对比一下“返回结构体实体”和“结构体指针作为参数”两种方式的汇编程序,可以看出返回结构体实体有更多的汇编语句,代码空间和运行时间都不讨巧,或许也是这个原因才会被大家慢慢遗忘“函数可以返回结构体实体”这件事。 
------------------------------------------------------------------------------------------------------------------------------------------------------- 
在这个试验中,也发现该编译器支持结构体实体A整体赋值给结构体实体B的方式,即g_tB = gt_A;下面在做个试验: 
[mw_shl_code=c,true] 
typedef struct _T 
{ 
    uint8 u8a; 
    uint8 u8b; 
    uint8 u8c; 
    uint8 u8d; 
    uint8 u8e; 
    uint8 u8f; 
    uint8 u8g; 
    uint8 u8h; 
}T_STRUCTURE; 
 
T_STRUCTURE g_tA, g_tB; 
 
int main (void) 
{        
    g_tB = g_tA;               /* 结构体整体赋值 */ 
 
    g_tB.u8a = g_tA.u8a;   /* 结构体成员依次赋值 */ 
    g_tB.u8b = g_tA.u8b; 
    g_tB.u8c = g_tA.u8c; 
    g_tB.u8d = g_tA.u8d; 
    g_tB.u8e = g_tA.u8e; 
    g_tB.u8f = g_tA.u8f; 
    g_tB.u8g = g_tA.u8g; 
    g_tB.u8h = g_tA.u8h; 
 
    return 0; 
} 
[/mw_shl_code] 
这次增加了结构体成员数量到8个,占2个32位空间,来对边一下汇编程序: 
5 
 
 
 
 
 
6 
 
 
上图是整体赋值的汇编代码,下图是成员依次赋值的代码,可见结构体整体赋值时,是按照4字节搬运的,这样搬运2次就可以完成8个字节的赋值;而成员依次赋值则是逐个字节依次搬运,空间和时间不讨巧。 
 
总结一下: 
1、函数不仅可以返回结构体指针,还可以返回结构体实体; 
2、如果想通过函数来修改结构体实体的值,“函数返回结构体实体”的方式在代码空间和运行时间上不及“结构体指针入参”的方式; 
3、结构体可以整体赋值给另一个结构体; 
4、在结构体成员比较多且零散的情况下,结构体整体赋值的方式在代码空间和运行时间上优于结构体成员依次赋值的方式; 
5、在下的C语言白学了。。。。。。 
 
P.S: 本人能力有限,汇编功底不足,分析有误之处望大神指点。上述实验现象可能比较依赖编译器,不一定完全成立。 
本次试验仅分析了简单的情况,更复杂的方式望各位大神进一步分析、分享,下面是“函数返回结构体实体”的试验代码。 
[mw_shl_code=c,true] 
typedef struct _T 
{ 
    uint8 u8a; 
    uint8 u8b; 
}T_STRUCTURE; 
 
T_STRUCTURE g_tA, g_tB; 
 
T_STRUCTURE test1(void) 
{ 
    T_STRUCTURE tC; 
    tC.u8a = 5; 
    tC.u8b = 6; 
    return tC; 
} 
 
T_STRUCTURE test2(void) 
{ 
    return g_tA; 
} 
 
void test3(T_STRUCTURE *ptD) 
{ 
    ptD->u8a = 3; 
    ptD->u8b = 4; 
    return; 
} 
 
int main (void) 
{    
    g_tA.u8a = 7; 
    g_tA.u8b = 8; 
    g_tB = test1();    /* 返回值为结构体实体 */ 
    printf("g_tB.u8a = %d\n", g_tB.u8a); 
    g_tB = test2();    /* 返回值为结构体实体 */ 
    printf("g_tB.u8a = %d\n", g_tB.u8a); 
    test3(&g_tB);      /* 通过结构体指针作为入参修改结构体值 */ 
    printf("g_tB.u8a = %d\n", g_tB.u8a); 
     
    g_tB = g_tA;       /* 结构体整体赋值 */ 
} 
[/mw_shl_code] 
 
 |   
 
 
 
 |