简体   繁体   English

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

[英]C string literal as parameter equals -1 in avr-gcc?

I am developing a software for AVR microcontroller.我正在为 AVR 微控制器开发软件。 Saying in fromt, now I only have LEDs and pushbuttons to debug.在 fromt 说,现在我只有 LED 和按钮来调试。 The problem is that if I pass a string literal into the following function:问题是,如果我将字符串文字传递给以下函数:

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

Somewhere in main()main()某个地方

test_char("AAAAA");

And now the LED changes state.现在 LED 会改变状态。 On my x86_64 machine I wrote the same function to compare (not LED, of course), but it turns out that str[0] equals to 'A' .在我的 x86_64 机器上,我编写了相同的函数进行比较(当然不是 LED),但结果是str[0]等于'A' Why is this happening?为什么会这样?

Update : Not sure whether this is related, but I have a struct called button , like this:更新:不确定这是否相关,但我有一个名为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
};

But it turned out that btn.seq_count start out as -1 though I defined it as 0.但结果是btn.seq_count-1开始,尽管我将其定义为 0。

Update2更新2

For the later problem, I solved by initializing the values in a function.对于后面的问题,我通过初始化函数中的值来解决。 However, that does not explain why seq_count was set to -1 in the previous case, nor does it explain why the character in string literal equals to -1 .然而,这并不能解释为什么seq_count在前一种情况下被设置为-1 ,也不能解释为什么字符串文字中的字符等于-1

Update3更新3

Back to the original problem, I added a complete mini example here, and same occurs:回到原来的问题,我在这里添加了一个完整的小例子,同样发生了:

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) {
  }
}

Update 4更新 4

I am trying to follow Nominal Animal's advice, but not quite successful.我正在尝试遵循 Nominal Animal 的建议,但并不十分成功。 Here is the code I have changed:这是我更改的代码:

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) {
  }
}

I am using gcc 4.6.4,我正在使用 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) 

Rewritten from scratch, to hopefully clear up some of the confusion.从头开始重写,希望能消除一些混乱。

First, some important background:首先,一些重要的背景:

AVR microcontrollers have separate address spaces for RAM and ROM/Flash ("program memory"). AVR 微控制器具有用于 RAM 和 ROM/闪存(“程序存储器”)的单独地址空间。

GCC generates code that assumes all data is always in RAM. GCC 生成的代码假定所有数据始终在 RAM 中。 (Older versions used to have special types, such as prog_char , that referred to data in the ROM address space, but newer versions of GCC do not and cannot support such data types.) (旧版本曾经有特殊类型,例如prog_char ,它引用 ROM 地址空间中的数据,但新版本的 GCC 不支持也不能支持这种数据类型。)

When linking against avr-libc, the linker adds code ( __do_copy_data ) to copy all initialized data from program memory to RAM.链接 avr-libc 时,链接器添加代码 ( __do_copy_data ) 以将所有初始化数据从程序存储器复制到 RAM。 If you have both avr-gcc and avr-libc packages installed, and you use something like avr-gcc -Wall -O2 -fomit-frame-pointer -mmcu=AVRTYPE source.c -o binary.elf to compile your source file into a program binary, then use avr-objcopy to convert the elf file into the format your firmware utilities support, you are linking against avr-libc.如果您同时安装了 avr-gcc 和 avr-libc 软件包,并且您使用类似avr-gcc -Wall -O2 -fomit-frame-pointer -mmcu=AVRTYPE source.c -o binary.elf将源文件编译为一个程序二进制文件,然后使用avr-objcopy将 elf 文件转换为您的固件实用程序支持的格式,您正在链接 avr-libc。

If you use avr-gcc to only produce an object file source.o , and some other utilities to link and upload your program to your microcontroller, this copying from program memory to RAM may not happen .如果您使用avr-gcc仅生成目标文件source.o和一些其他实用程序来链接您的程序并将其上传到您的微控制器,则可能不会发生从程序存储器复制到 RAM 的情况 It depends on what linker and libraries your use.这取决于您使用的链接器和库。

As most AVRs have only a few dozen to few hundred bytes of RAM available, it is very, very easy to run out of RAM.由于大多数 AVR 只有几十到几百字节的可用 RAM,因此很容易耗尽 RAM。 I'm not certain if avr-gcc and avr-libc reliably detect when you have more initialized data than you have RAM available.我不确定 avr-gcc 和 avr-libc 是否能够可靠地检测到初始化数据多于可用 RAM 的数据。 If you specify any arrays containing strings, it is very likely you're already overrun your RAM, causing all sorts of interesting bugs to appear.如果您指定任何包含字符串的数组,很可能您已经超出了您的 RAM,从而导致出现各种有趣的错误。

The avr/pgmspace.h header file is part of avr-libc, and defines a macro, PROGMEM , that can be used to specify data that will only be referred to by functions that take program memory addresses (pointers), such as pgm_read_byte() or strcmp_P() defined in the same header file. avr/pgmspace.h头文件是 avr-libc 的一部分,并定义了一个宏PROGMEM ,该宏可用于指定仅由采用程序内存地址(指针)的函数引用的数据,例如pgm_read_byte()strcmp_P()定义在同一头文件中。 The linker will not copy such variables to RAM -- but neither will the compiler tell you if you're using them wrong.链接器不会将这些变量复制到 RAM 中——但编译器也不会告诉您是否使用错误。

If you use both avr-gcc and avr-libc, I recommend using the following approach for all read-only data:如果您同时使用 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);
    }
}

Because of changes (new limitations) in GCC internals, the PROGMEM attribute can only be used with a variable;由于 GCC 内部的变化(新限制), PROGMEM属性只能与变量一起使用; if it refers to a type, it does nothing.如果它指的是一个类型,它什么都不做。 Therefore, you need to specify strings as character arrays, using one of the forms above.因此,您需要使用上述形式之一将字符串指定为字符数组。 ( example1 is visible within this compilation unit only, example2 can be referred to from other compilation units too, and example3 is visible only in the function it is defined in. Here, visible refers to where you can refer to the variable; it has nothing to do with the contents.) example1只在这个编译单元中可见, example2也可以被其他编译单元引用, example3只在它定义的函数中可见。这里, visible是指你可以引用变量的地方;它什么都没有与内容有关。)

The PROGMEM attribute does not actually change the code GCC generates. PROGMEM属性实际上并没有改变 GCC 生成的代码。 All it does is put the contents to .progmem.data section, iff without it they'd be in .rodata .它所做的就是将内容放到.progmem.data部分,如果没有它,它们就会在.rodata All of the magic is really in the linking, and in linked library code.所有的魔法都在链接和链接库代码中。

If you do not use avr-libc, then you need to be very specific with your const attributes, as they determine which section the contents will end up in. Mutable (non-const) data should end up in the .data section, while immutable (const) data ends up in .rodata section(s).如果你使用 avr-libc,那么你需要对你的const属性非常具体,因为它们决定了内容将在哪个部分结束。可变(非常量)数据应该在.data部分结束,而不可变(const)数据在.rodata部分结束。 Remember to read the specifiers from right to left, starting at the variable itself, separated by '*': the leftmost refers to the content, whereas the rightmost refers to the variable.请记住从右到左读取说明符,从变量本身开始,用“*”分隔:最左边的是内容,而最右边的是变量。 In other words,换句话说,

const char *s = p;

defines s so that the value of the variable can be changed, but the content it points to is immutable (unchangeable/const);定义s以便变量的值可以改变,但它指向的内容是不可变的(unchangeable/const); whereas然而

char *const s = p;

defines s so that you cannot modify the variable itself, but you can the content -- the content s points to is mutable, modifiable.定义s ,这样就可以不修改变量本身,而是可以在内容-内容s点是可变的,修改的。 Furthermore,此外,

const char *s = "literal";

defines s to point to a literal string (and you can modify s , ie. make it point to some other literal string for example), but you cannot modify the contents;s定义为指向一个文字字符串(并且您可以修改s ,例如,使其指向某个其他文字字符串),但您不能修改其内容; and

char s[] = "string";

defines s to be a character array (of length 6; string length + 1 for end-of-string char), that happens to be initialized to { 's', 't', 'r', 'i', 'n', 'g', '\\0' } .s定义为一个字符数组(长度为 6;字符串长度 + 1 表示字符串结尾字符),恰好被初始化为{ 's', 't', 'r', 'i', 'n', 'g', '\\0' }

All linker tools that work on object files use the sections to determine what to do with the contents.所有处理目标文件的链接器工具都使用这些部分来确定如何处理内容。 (Indeed, avr-libc copies the contents of .rodata sections to RAM, and only leaves .progmem.data in program memory.) (实际上,avr-libc 将.rodata部分的内容复制到 RAM,并且只将.progmem.data在程序存储器中。)


Carl Dong, there are several cases where you may observe weird behaviour, even reproducible weird behaviour. Carl Dong,有几种情况您可能会观察到奇怪的行为,甚至是可重现的奇怪行为。 I'm no longer certain which one is the root cause of your problem, so I'll just list the ones I think are likely:我不再确定哪一个是您问题的根本原因,因此我将列出我认为可能的问题:

  1. If linking against avr-libc, running out of RAM如果链接到 avr-libc,则内存不足

    AVRs have very little RAM, and copying even string literals to RAM easily eats it all up. AVR 的 RAM 非常少,即使将字符串文字复制到 RAM 中也很容易把它吃光。 If this happens, any kind of weird behaviour is possible.如果发生这种情况,任何奇怪的行为都是可能的。

  2. Failing to linking against avr-libc无法链接到 avr-libc

    If you think you use avr-libc, but are not certain, then use avr-objdump -d binary.elf | grep -e '^[0-9a-f]* <_'如果你认为你使用 avr-libc,但不确定,那么使用avr-objdump -d binary.elf | grep -e '^[0-9a-f]* <_' avr-objdump -d binary.elf | grep -e '^[0-9a-f]* <_' to see if the ELF binary contains any library code. avr-objdump -d binary.elf | grep -e '^[0-9a-f]* <_'以查看 ELF 二进制文件是否包含任何库代码。 You should expect to see at least <__do_clear_bss>: , <_exit>: , and <__stop_program>: in that list, I believe.我相信,您应该期望在该列表中至少看到<__do_clear_bss>:<_exit>:<__stop_program>:

  3. Linking against some other C library, but expecting avr-libc behaviour链接到其他一些 C 库,但期望 avr-libc 行为

    Other libraries you link against may have different rules.您链接的其他库可能有不同的规则。 In particular, if they're designed to work with some other C compiler -- especially one that supports multiple address spaces, and therefore can deduce when to use ld and when lpm based on types --, it might be impossible to use avr-gcc with that library, even if all the tools talk to each other nicely.特别是,如果它们被设计用来工作与其他C编译器-尤其是一个支持多个地址空间,因此可以推断,当使用ld以及何时lpm基于类型- ,这可能是无法使用AVC-使用该库的 gcc,即使所有工具都可以很好地相互通信。

  4. Using a custom linker script and a freestanding environment (no C library at all)使用自定义链接器脚本和独立环境(根本没有 C 库)

    Personally, I can live with immutable data ( .rodata sections) being in program memory, with myself having to explicitly copy any immutable data to RAM whenever needed.就个人而言,我可以忍受程序内存中的不可变数据( .rodata部分),而我自己必须在需要时将任何不可变数据显式复制到 RAM。 This way I can use a simple microcontroller-specific linker script and GCC in freestanding mode (no C library at all used), and get complete control over the microcontroller.通过这种方式,我可以在独立模式下使用简单的特定于微控制器的链接器脚本和 GCC(根本不使用 C 库),并完全控制微控制器。 On the other hand, you lose all the nice predefined macros and functions avr-libc and other C libraries provide.另一方面,你失去了 avr-libc 和其他 C 库提供的所有好的预定义宏和函数。

    In this case, you need to understand the AVR architecture to have any hope of getting sensible results.在这种情况下,您需要了解 AVR 架构才能有任何希望获得合理的结果。 You'll need to set up the interrupt vectors and all kinds of other stuff to get even a minimal do-nothing loop to actually run;您需要设置中断向量和各种其他东西,才能真正运行最小的无操作循环; personally, I read all the assembly code GCC produces (from my own C source) simply to see if it makes sense, and to try to make sure it all gets processed correctly.就我个人而言,我阅读了 GCC 生成的所有汇编代码(来自我自己的 C 源代码)只是为了看看它是否有意义,并尝试确保所有这些代码都得到正确处理。


Questions?问题?

I faced a similar problem (inline strings were equal to 0xff,0xff,...) and solved it by just changing a line in my Makefile我遇到了类似的问题(内联字符串等于 0xff,0xff,...)并通过更改我的 Makefile 中的一行来解决它

from :从 :

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

to :到 :

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

or seems better :或者看起来更好:

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

You can see full problem and answer here : https://www.avrfreaks.net/comment/2943846#comment-2943846你可以在这里看到完整的问题和答案: https : //www.avrfreaks.net/comment/2943846#comment-2943846

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM