2016-09-12 48 views
3

取决于C编译器和编译器标志的版本,可以在函数的任何地方初始化变量(据我所知)。C函数和变量的内存分配C

我习惯于把所有的变量放在函数的顶部,但是如果在函数的任何其他地方定义了变量的话,讨论就开始关于变量的内存使用。

下面我写了2个简短的例子,我想知道是否有人能解释我(或验证)内存如何分配。

示例1:变量y在可能的返回语句之后定义,有可能这个变量不会用于这个原因,据我所知这并不重要,代码(内存分配)如果变量放在函数的顶部,它将是相同的。它是否正确?示例2:变量x在一个循环中被初始化,这意味着这个变量的范围只在这个循环中,但是这个变量的内存使用情况如何呢?如果放在函数的顶部,它会有什么不同吗?或者只是在函数调用时在堆栈上初始化?

编辑:总结一个主要问题: 减少变量的范围或改变第一次使用的位置(所以在其他地方而不是顶部)对内存使用有什么影响吗?

代码示例1

static void Function(void){ 
uint8_t x = 0; 

//code changing x 
if(x == 2) 
{ 
    return; 
} 

uint8_t y = 0;  
//more code changing y 
} 

代码示例2

static void LoopFunction(void){ 
uint8_t i = 0; 

for(i =0; i < 100; i ++) 
{ 
    uint8_t x = i; 
    // do some calculations 
    uartTxLine("%d", x); 
} 

//more code 
} 

回答

5

我习惯把它所有的变量在函数的顶部

此使用在旧版本的C中是必需的,但现代编译器已经放弃了这个要求。只要他们知道首次使用时的变量类型,编译器就可以获得所需的所有信息。

我想知道是否有人能解释我如何分配内存。

编译器决定如何在自动存储区中分配内存。实现不限于为每个变量声明一个单独位置的方法。允许它们重用超出范围的变量位置,也可以重用某个点后不再使用的变量。

在你的第一个例子中,变量y被允许使用以前由可变x所占用的空间,因为使用的y的第一点是利用x的最后一点后。

在第二个示例中,循环内用于x的空间可以重用于您可能在// more code区域中声明的其他变量。

+0

好了,这意味着它实际上是更有效的这种方式,而不是代码时把所有的变量在顶部的地方可能不是很清楚,当它超出了范围或何时使用?或者这仍然没有关系? – koldewb

+6

@koldewb编译器无关紧要,因为它可以计算出变量的活动使用区域,并相应地分配内存。然而,阅读你的代码的人很重要,所以把变量放在离他们使用的地方更近的地方比在顶部声明所有东西要好。 – dasblinkenlight

1

基本上,故事是这样的。在原始汇编程序中调用函数时,可以自定义在进入函数时将函数使用的所有内容存储在堆栈中,并在离开时进行清理。某些CPU和ABI可能有一个涉及自动堆栈参数的调用约定。

可能是因为这个原因,C和许多其他旧语言都要求所有变量都必须在函数的顶部(或在作用域之上)声明,以便{ }反映堆栈上的push/pop 。

大约在80年代/ 90年代,编译器开始有效地优化这些代码,因为它们只会在首次使用时为局部变量分配空间,并且在没有进一步使用的时候解除分配为了它。无论变量是在哪里声明的 - 对于优化编译器都没有关系。大约在同一时间,C++解除了C所拥有的变量声明限制,并允许在任何地方声明变量。然而,C在1999年之前并没有在更新的C99标准之前真正解决这个问题。在现代C中,你可以在任何地方声明变量。

所以你的两个例子之间绝对没有性能差异,除非你使用了一个令人难以置信的古老编译器。然而,尽可能地缩小变量的范围是一种很好的编程习惯 - 尽管不应该以牺牲可读性为代价来完成。

虽然只是一个风格问题,我个人喜欢写你的函数是这样的:

(请注意,您正在使用uint8_t错误的printf格式指示符)

#include <inttypes.h> 

static void LoopFunction (void) 
{ 
    for(uint8_t i=0; i < 100; i++) 
    { 
    uint8_t x = i; 
    // do some calculations 
    uartTxLine("%" PRIu8, x); 
    } 

//more code 
} 
0

老C只允许声明(并初始化)块顶部的变量。你在哪里允许任何地方初始化一个新块(一对{}字符)块内部,所以你旁边的代码中使用它们了则声明变量的可能性:

... /* inside a block */ 
{ int x = 3; 
    /* use x */ 
} /* x is not adressabel past this point */ 

而且你在哪里允许在switch陈述,if报表和whiledo语句做到这一点(无处不在那里你可以初始化一个新的块)

现在,你被允许声明一个变量任何地方的声明是允许的,并且该变量的范围变从宣言到内部结束你已经把它声明为。

编译器决定何时为局部变量分配存储空间,因此,当您创建堆栈帧时(这是gcc方式,因为它只分配一次局部变量),或者当您输入块定义(例如,Microsoft C就是这样做的)在运行时分配空间是需要在运行时推进堆栈指针的,因此如果每个堆栈帧只执行一次,那么您将节省CPU周期(但浪费内存位置)。这里最重要的是你不允许在其范围定义之外引用一个可变位置,所以如果你尝试这样做,你会得到未定义的行为。由于没有人花时间使用Microsoft-C编译器(在核心转储中失败)编译该程序,而不是使用GCC编译该程序,所以我发现了一个老的bug在Internet上长时间运行。该代码使用在内部作用域中定义的局部变量(if语句的then部分)通过在代码的其他部分中引用(因为所有内容都在main函数中,堆栈帧始终存在)Microsoft-C只是在退出if声明时重新分配了空间,但GCC等待完成,直到完成main。通过在变量声明中添加一个static修饰符(使其成为全局变量)并且不再需要重构来解决这个问题是非常必要的。

int main() 
{ 
    struct bla_bla *pointer_to_x; 
    ... 
    if (something) { 
     struct bla_bla x; 
     ... 
     pointer_to_x = &x; 
    } 
    /* x does not exist (but it did in gcc) */ 
    do_something_to_bla_bla(pointer_to_x); /* wrong, x doesn't exist */ 
} /* main */ 

时改为:

int main() 
{ 
    struct bla_bla *pointer_to_x; 
    ... 
    if (something) { 
     static struct bla_bla x; /* now global ---even if scoped */ 
     ... 
     pointer_to_x = &x; 
    } 
    /* x is not visible, but exists, so pointer_to_x continues to be valid */ 
    do_something_to_bla_bla(pointer_to_x); /* correct now */ 
} /* main */