C语言中函数和变量的内存分配

C语言中函数和变量的内存分配,c,memory,stack,embedded,C,Memory,Stack,Embedded,根据C编译器的版本和编译器标志,可以在函数中的任何位置初始化变量(据我所知) 我习惯于将所有变量放在函数的顶部,但是讨论开始于变量的内存使用,如果在函数的任何其他位置定义的话 下面我写了两个简短的例子,我想知道是否有人可以向我解释(或验证)内存是如何分配的 示例1:变量y是在一个可能的return语句之后定义的,因此这个变量可能不会被使用,据我所知,这并不重要,如果变量放在函数的顶部,代码(内存分配)将是相同的。这是正确的吗 示例2:变量x在循环中初始化,这意味着该变量的作用域仅在此循环中,但该

根据C编译器的版本和编译器标志,可以在函数中的任何位置初始化变量(据我所知)

我习惯于将所有变量放在函数的顶部,但是讨论开始于变量的内存使用,如果在函数的任何其他位置定义的话

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

示例1:变量y是在一个可能的return语句之后定义的,因此这个变量可能不会被使用,据我所知,这并不重要,如果变量放在函数的顶部,代码(内存分配)将是相同的。这是正确的吗

示例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
}
静态void循环函数(void){
uint8_t i=0;
对于(i=0;i<100;i++)
{
uint8_t x=i;
//做一些计算
uartTxLine(“%d”,x);
}
//更多代码
}
我习惯于把所有变量都放在函数的顶部

这在旧版本的C中曾经是必需的,但现代编译器放弃了这一要求。只要他们在第一次使用时知道变量的类型,编译器就拥有他们所需要的所有信息

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

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

在第一个示例中,允许变量
y
使用变量
x
以前占用的空间,因为
y
的第一个使用点位于
x
的最后一个使用点之后


在第二个示例中,循环中用于
x
的空间可以重新用于其他变量,您可以在
//更多代码
区域中声明这些变量。

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

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

大约在80/90年代左右,编译器开始高效地优化此类代码,因为他们只会在局部变量首次使用时为其分配空间,而在不再使用时取消分配空间。无论该变量在何处声明,对于优化编译器来说都无关紧要

同时,C++取消了C所具有的变量声明限制,并允许变量在任何地方声明。然而,C在1999年之前并没有用更新的C99标准来解决这个问题。在现代C语言中,您可以到处声明变量

因此,除非您使用的是非常古老的编译器,否则两个示例之间绝对没有性能差异。然而,尽可能缩小变量的范围被认为是良好的编程实践,尽管这样做不应该以牺牲可读性为代价

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

(请注意,您对uint8\u t使用了错误的printf格式说明符)

#包括
静态void循环函数(void)
{
对于(uint8_t i=0;i<100;i++)
{
uint8_t x=i;
//做一些计算
uartTxLine(“%”PRIu8,x);
}
//更多代码
}

旧C只允许在块的顶部声明(和初始化)变量。您可以在块内的任何位置初始化一个新块(一对
{
}
字符),这样您就可以使用它们在代码旁边声明变量:

... /* inside a block */
{ int x = 3;
    /* use x */
} /* x is not adressabel past this point */
您可以在
switch
语句、
if
语句和
while
do
语句中执行此操作(可以初始化新块的任何地方)

现在,您可以在允许语句的任何位置声明变量,并且该变量的范围从声明点一直到您声明它的内部嵌套块的末尾

编译器决定何时为局部变量分配存储,因此,您可以在创建堆栈帧时(这是gcc方式,因为它只分配一次局部变量)或在定义块中输入时(例如,Microsoft C采用这种方式)分配所有这些存储在运行时分配空间需要在运行时推进堆栈指针,因此,如果每个堆栈帧只执行一次,则会节省cpu周期(但会浪费内存位置)。这里重要的一点是,不允许您引用其范围定义之外的变量位置,因此如果您尝试这样做,您将得到未定义的行为。我发现了一个在互联网上运行了很长时间的老bug,因为没有人花时间使用Microsoft-C编译器编译那个程序(失败了)
... /* inside a block */
{ int x = 3;
    /* use x */
} /* x is not adressabel past this point */
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 */