在avr-gcc中,C字符串文字作为参数等于-1?

时间:2014-09-13 19:24:06

标签: c string gcc avr string-literals

我正在为AVR微控制器开发软件。在fromt说,现在我只有LED和按钮来调试。问题是,如果我将字符串文字传递给以下函数:

void test_char(const char *str) {
  if (str[0] == -1)
    LED_PORT ^= 1 << 7;         /* Test */
}

main()

中的某个地方
test_char("AAAAA");

现在LED改变了状态。在我的x86_64机器上,我编写了相同的功能进行比较(当然不是LED),但事实证明str[0]等于'A'。为什么会这样?

更新: 不确定这是否相关,但我有一个名为button的结构,如下所示:

typedef struct {
  int8_t seq[BTN_SEQ_COUNT];    /* The sequence of button */
  int8_t seq_count;             /* The number of buttons registered */
  int8_t detected;              /* The detected button */
  uint8_t released;             /* Whether the button is released
                                   after a hold */
} button;
button btn = {
  .seq = {-1, -1, -1},
  .detected = -1,
  .seq_count = 0,
  .released = 0
};

但事实证明,btn.seq_count-1开头,但我将其定义为0.

UPDATE2

对于后来的问题,我通过初始化函数中的值来解决。但是,这并不能解释为什么seq_count在前一种情况下被设置为-1,也没有解释为什么字符串文字中的字符等于-1

UPDATE3

回到原来的问题,我在这里添加了一个完整的迷你示例,同样的情况发生了:

void LED_on() {
  PORTA = 0x00;
}

void LED_off() {
  PORTA = 0xFF;
}

void port_init() {
  PORTA = 0xFF;
  DDRA |= 0xFF;
}

void test_char(const char* str) {
    if (str[0] == -1) {
        LED_on();
    }
}

void main() {
  port_init();
  test_char("AAAAA");
  while(1) {
  }
}

更新4

我正在尝试遵循Nominal Animal的建议,但不太成功。这是我改变的代码:

void test_char(const char* str) {
    switch(pgm_read_byte(str++)) {
        case '\0': return;
        case 'A': LED_on(); break;
        case 'B': LED_off(); break;
    }
}
void main() {
  const char* test = "ABABA";
  port_init();
  test_char(test);
  while(1) {
  }
}

我正在使用gcc 4.6.4,

avr-gcc -v
Using built-in specs.
COLLECT_GCC=avr-gcc
COLLECT_LTO_WRAPPER=/home/carl/Softwares/AVR/libexec/gcc/avr/4.6.4/lto-wrapper
Target: avr
Configured with: ../configure --prefix=/home/carl/Softwares/AVR --target=avr --enable-languages=c,c++ --disable-nls --disable-libssp --with-dwarf2
Thread model: single
gcc version 4.6.4 (GCC) 

2 个答案:

答案 0 :(得分:2)

从头开始改写,希望能解决一些困惑。

首先,一些重要的背景:

AVR微控制器具有独立的RAM和ROM / Flash地址空间(&#34;程序存储器&#34;)。

GCC生成的代码假定所有数据始终在RAM中。 (旧版本曾经有特殊类型,例如prog_char,它们引用了ROM地址空间中的数据,但较新版本的GCC不支持并且不支持此类数据类型。)

当链接avr-libc时,链接器添加代码(__do_copy_data)以将所有初始化数据从程序存储器复制到RAM。如果您同时安装了avr-gcc和avr-libc软件包,并且使用avr-gcc -Wall -O2 -fomit-frame-pointer -mmcu=AVRTYPE source.c -o binary.elf之类的东西将源文件编译为程序二进制文件,则使用avr-objcopy将elf文件转换为您的格式固件实用程序支持,您正在链接avr-libc。

如果您使用avr-gcc仅生成目标文件source.o,以及一些其他实用程序来链接程序并将程序上传到您的微控制器,从程序存储器复制到RAM可能不会发生。这取决于您使用的链接器和库。

由于大多数AVR只有几十到几百字节的RAM可用,因此RAM的运行非常非常容易。我不确定avr-gcc和avr-libc是否可靠地检测到何时有更多初始化数据而不是RAM可用。如果你指定任何包含字符串的数组,很可能你已经超出你的RAM,导致出现各种有趣的错误。

avr/pgmspace.h头文件是avr-libc的一部分,定义了一个宏PROGMEM,可用于指定只接受程序存储器地址的函数引用的数据(指针),例如在同一个头文件中定义的pgm_read_byte()strcmp_P()。链接器不会将这些变量复制到RAM中 - 但编译器也不会告诉您是否错误地使用它们。

如果同时使用avr-gcc和avr-libc,我建议对所有只读数据使用以下方法:

#include <avr/pgmspace.h>

/*
 * Define LED_init(), LED_on(), and LED_off() functions.
*/

void blinky(const char *str)
{
    while (1) {
        switch (pgm_read_byte(str++)) {
            case '\0': return;
            case 'A':  LED_on();  break;
            case 'B':  LED_off(); break;
        }

        /* Add a sleep or delay here,
         * or you won't be able to see the LED flicker. */
    }
}

static const char example1[] PROGMEM = "AB";
const char example2[] PROGMEM = "AAAA";

int main(void)
{
    static const char example3[] PROGMEM = "ABABB";

    LED_init();

    while (1) {
        blinky(example1);
        blinky(example2);
        blinky(example3);
    }
}

由于GCC内部的更改(新限制),PROGMEM属性只能与变量一起使用;如果它指的是一种类型,它什么都不做。因此,您需要使用上面的表单之一将字符串指定为字符数组。 (example1仅在此编译单元中可见,example2也可以从其他编译单元引用,example3仅在其定义的函数中可见。这里, visible 指的是你可以引用变量的地方;它与内容无关。)

PROGMEM属性实际上并不会更改GCC生成的代码。它所做的只是将内容放到.progmem.data部分,如果没有它,它们就会出现在.rodata中。所有的魔力都在链接和链接库代码中。

如果您使用avr-libc,那么您需要非常具体地使用const属性,因为它们确定内容将最终进入哪个部分.Mutable(非-const)数据应该在.data部分结束,而不可变(const)数据最终在.rodata部分结束。请记住从右到左阅读说明符,从变量本身开始,用&#39; *&#39;分隔:最左边指内容,而最右边指的是变量。换句话说,

const char *s = p;

定义s,以便可以更改变量的值,但它指向的内容是不可变的(不可更改的/ const);而

char *const s = p;

定义s,以便您无法修改变量本身,但内容 - 内容s指向的内容是可变的,可修改的。此外,

const char *s = "literal";

s定义为指向文字字符串(并且您可以修改s,即。例如,使其指向其他文字字符串),但您无法修改内容;和

char s[] = "string";

s定义为一个字符数组(长度为6;字符串长度为1,用于字符串结尾字符串),恰好初始化为{ 's', 't', 'r', 'i', 'n', 'g', '\0' }

所有处理目标文件的链接器工具都使用这些部分来确定如何处理内容。 (实际上,avr-libc将.rodata部分的内容复制到RAM中,并且只在程序存储器中留下.progmem.data。)


Carl Dong,在某些情况下,您可能会观察到奇怪的行为,甚至是可重复的奇怪行为。我不再确定哪一个是你问题的根本原因,所以我只列出我认为可能的那些:

  1. 如果链接avr-libc,用完RAM

    AVR的RAM很少,甚至将字符串文字复制到RAM也很容易将其全部用掉。如果发生这种情况,任何类型的奇怪行为都是可能的。

  2. 无法链接avr-libc

    如果您认为使用avr-libc但不确定,请使用avr-objdump -d binary.elf | grep -e '^[0-9a-f]* <_'查看ELF二进制文件是否包含任何库代码。我相信,您应该会在该列表中看到至少<__do_clear_bss>:<_exit>:<__stop_program>:

  3. 链接其他一些C库,但期望avr-libc行为

    您链接的其他图书馆可能有不同的规则。特别是,如果它们被设计为与其他C编译器一起工作 - 尤其是支持多个地址空间的编译器,因此可以推断何时使用ld以及何时lpm基于类型 - - 即使所有工具都很好地相互通信,也可能无法在该库中使用avr-gcc。

  4. 使用自定义链接描述文件和独立环境(根本没有C库)

    就个人而言,我可以使用不可变数据(.rodata部分)在程序存储器中,我自己必须在需要时将任何不可变数据显式复制到RAM。通过这种方式,我可以在独立模式下使用简单的微控制器专用链接器脚本和GCC(根本不使用C库),并完全控制微控制器。另一方面,你失去了avr-libc和其他C库提供的所有漂亮的预定义宏和函数。

    在这种情况下,您需要了解AVR架构才能获得合理的结果。你需要设置中断向量和各种其他东西,以获得实际运行的最小无操作循环;就个人而言,我阅读GCC生成的所有汇编代码(来自我自己的C源代码),只是为了确定它是否有意义,并尝试确保所有汇编代码都正确处理。


  5. 有问题吗?

答案 1 :(得分:0)

我遇到了类似的问题(内联字符串等于0xff,0xff,...),并通过仅更改Makefile中的一行来解决了该问题

来自:

.out.hex:
    $(OBJCOPY)  -j .text                    \
                -j .data                       \
                -O $(HEXFORMAT) $< $@

至:

.out.hex:
    $(OBJCOPY)  -j .text                    \
                -j .data                       \
                -j .rodata                     \
                -O $(HEXFORMAT) $< $@

或似乎更好:

.out.hex:
    $(OBJCOPY)  -R .fuse                    \
                -R .lock                       \
                -R .eeprom                     \
                -O $(HEXFORMAT) $< $@

您可以在此处看到完整的问题并回答:https://www.avrfreaks.net/comment/2943846#comment-2943846