一次又一次初始化时,指针在内部循环中做什么?

时间:2019-04-15 08:10:56

标签: c loops pointers gcc char

我正在使用GNU ARM嵌入式工具链处理STM32 uC。我试图找出在循环内初始化指针时会发生什么。下面是一个非常简单的示例(部分为伪代码):

while(1)
{
    char* msg = "my message";
    transmit_via_uart(msg, strlen(msg));
    delay(1000);
}

每次指针msg再次初始化时,处理器是否为堆上的字符串分配新的空间?还是覆盖“旧”指针msg指向的空间(未分配新空间)?

我知道,我可以将初始化行放在while循环上方,我很好奇会发生什么,无法弄清楚。

感谢您的快速解答! T。

编辑:对不起!当然,编译器不会分配任何东西...:)

4 个答案:

答案 0 :(得分:3)

除非明确使用malloc系列函数,否则C程序永远不会在堆上分配。

字符串文字"my message"存储在ROM中(在大多数系统中,可能存储在称为.rodata.text的部分中)。程序启动时就分配在那里。

msg指针仅指向ROM中的该地址。指针本身分配在堆栈或CPU寄存器中。

然而,编译器足够聪明,即使您在循环中重复调用该地址,其地址也不会改变。因此,它很可能会优化掉变量msg并简单地将可以找到字符串的原始,硬编码的ROM地址传递给该函数。

除非正在使用30岁的C90编译器,否则可以将初始化放在循环的上方。


请注意,编写代码的更好方法是:

char msg[] = "my message";
transmit_via_uart(msg, sizeof(msg)-1);

这样,您可以在编译时计算字符串文字的大小,因为它是恒定的并且是已知的。通过使用strlen,您可以强制执行运行时计算,以使编译器可能不够聪明,无法进行优化。

答案 1 :(得分:3)

  

编译器是否为堆上的字符串分配新空间?   指针msg再次初始化的时间?还是覆盖   msg“旧”指针指向的空间(未分配新空间)?

不。字符串 literal 在编译时是已知的,因此编译器可以将其存储在可执行文件的特殊部分(通常为.text)中。需要时,编译器可以简单地使用指向存储文字的那部分内存的指针。 Ne需要自己执行char的任何副本。

经过完全优化(即-O3 )编译后,您的代码(source here)如下所示:

.LC0:
        .string "my message"
ff():
        sub     rsp, 8
.L2:
        mov     esi, 10
        mov     edi, OFFSET FLAT:.LC0
        call    transmit_via_uart(char const*, int)
        mov     edi, 1000
        call    delay(unsigned int)
        jmp     .L2

每个循环(.L02部分)唯一初始化的是指针,该指针使用指令.LC0mov edi, OFFSET FLAT:.LC0处获取已知内存的地址。 >

没有动态分配内存,如果您考虑一下,为什么在编译时知道了我们需要的所有信息后又何乐而不为呢?

答案 2 :(得分:2)

在C语言中,所有文字字符串实际上都是字符的只读数组,这些数组当然包括空终止符。当您获得指向此类字符串的指针时,您将获得指向其第一个元素(字符串中的第一个字符)的指针。

此数组的确切存储位置无关紧要,但是每个字符串文字通常只有一个副本。

对于变量msg本身,很可能是编译器在调用函数时为其分配空间以及函数中所有其他局部变量。在进入循环之前,变量的空间可能尚未初始化。然后,好的编译器会对其进行优化,从而使变量仅初始化一次。

答案 3 :(得分:-1)

  

每次指针msg再次初始化时,处理器是否为堆上的字符串分配新的空间?

源代码中的字符串文字表示在整个程序执行过程中存在的字符数组。因此,在程序开始执行时为其提供了空间。

编译器执行此操作的典型方法是将字符串放入程序的常量数据部分。

  

还是覆盖了msg指向“旧”指针的空间(未分配新空间)?

在C语义中,每次到达msg的定义时,都会创建一个名为msg的对象并将其初始化以指向字符数组。

在实践中,好的编译器(尤其是启用了优化的情况下)将认识到这对于实现源代码的最终效果不是必需的。对于调用transmit_via_uart(msg, strlen(msg));,一个好的编译器将同时知道msg的值(相对于存储字符串的程序段)和strlen(msg)的值,并会生成指令将这些值传递给transmit_via_uart,而无需为msg对象使用实际的存储空间。

使用以下方法可使编译器更清楚其含义:

while(1)
{
    static const char msg[] = "my message";
    transmit_via_uart(msg, sizeof msg - 1);
    delay(1000);
}

msg声明为staticconst会明确告诉编译器msg是不变数据的永久数组,而使用sizeof则告诉编译器该值是对象的固定属性,而不是在运行时使用strlen进行计算的值(尽管从技术上讲,它仍然是运行时表达式,而不是编译时常量)。一个无法优化原始代码的低质量编译器可能会对此代码做得更好。