【问题标题】:How to declare 16-bits pointer to string in GCC C compiler for arm processor如何在 GCC C 编译器中为 arm 处理器声明指向字符串的 16 位指针
【发布时间】:2020-05-12 20:48:19
【问题描述】:

我尝试在 ARM Cortex-M0 处理器的 GNU GCC C 编译器中声明一个指向字符串的短指针数组(16 位而不是默认的 32 位),以减少闪存消耗。我有两种语言的大约 200 个字符串,因此将指针的大小从 32 位减少到 16 位可以节省 800 字节的闪存。这应该是可能的,因为闪存大小小于 64 kB,所以指向闪存的指针的高位字(16 位)是常量,等于 0x0800:

const unsigned char str1[] ="First string";
const unsigned char str2[] ="Second string";
const unsigned short ptrs[] = {&str1, &str2};    //this line generate error

但我在第 3 行出现错误

“错误:初始化元素在加载时不可计算”

然后我尝试了:

const unsigned short ptr1 = (&str1 & 0xFFFF);

我得到了: “错误:二进制 & 的操作数无效(有 'const unsigned char (*)[11]' 和 'int')”

经过多次尝试,我最终完成了组装:

  .section .rodata.strings
  .align 2
ptr0:
ptr3:   .short (str3-str0)
ptr4:   .short (str4-str0)

str0:
str3:   .asciz  "3-th string"
str4:   .asciz  "4-th string"

编译顺利通过,但现在我在尝试引用指针时遇到问题:来自 C 代码的 ptr4 和 ptr0。尝试将“ptr4-ptr0”作为 8 位参数传递给 C 函数:

ptr = getStringFromTable (ptr4-ptr0)

声明为:

const unsigned char* getStringFromTable (unsigned char stringIndex)

我得到了这样的错误代码:

ldr     r3, [pc, #28]   ; (0x8000a78 <main+164>)
ldrb    r1, [r3, #0]
ldr     r3, [pc, #28]   ; (0x8000a7c <main+168>)
ldrb    r3, [r3, #0]
subs    r1, r1, r3
uxtb    r1, r1
bl      0x8000692 <getStringFromTable>

而不是这样的:

movs    r0, #2
bl      0x8000692 <getStringFromTable>

如有任何建议,我将不胜感激。

.....几天后.....

按照@TonyK 和@old_timer 的建议,我终于通过以下方式解决了这个问题: 在汇编中我写道:

  .global str0,  ptr0
  .section .rodata.strings
  .align 2
ptr0:   .short (str3-str0)
        .short (str4-str0)

str0:
str3:   .asciz  "3-th string"
str4:   .asciz  "4-th string"

然后我在 C 中声明:

extern unsigned short ptr0[];
extern const unsigned char str0[] ;

enum ptrs {ptr3, ptr4};        //automatically: ptr3=0, ptr4=1

const unsigned char* getStringFromTable (enum ptrs index)
  {
  return &str0[ptr0[index]] ;
  }

现在是这段文字:

ptr = getStringFromTable (ptr4)

被编译成正确的代码:

08000988: 0x00000120   movs    r0, #1
0800098a: 0xfff745ff   bl      0x8000818 <getStringFromTable>

我只需要记住保持enum ptrs 的顺序,每次我将向程序集添加一个字符串并将一个新项目添加到enum ptrs

【问题讨论】:

  • arm 地址是 32 位的,什么是 16 位指针,您将如何使用它? (好吧,有些是 24 岁)
  • 如果你想要一个偏移量,然后你将在以后制作一个完整的地址,然后 const unsigned short ptr1 = (&str1)&0xFFFF;然后用一些base+ptr1的味道指向它。给这只猫换皮的各种方法,如果这都是 const 的东西,那么从高级语言的角度来看,所有的字符串都可以放在一个数组中,然后你只需要偏移量来访问不是完整地址而不是物理地址。即使不是 const 你也可以用二维数组做到这一点,只需要一个偏移量,不需要特殊的指针数学(也不需要链接时的物理地址)
  • 可以创建一个临时工具,以某种形式预先准备数据,这样实际的二进制文件就不会携带太多的包袱,主程序也不必获取工具链来尝试减少编译时的使用时间。
  • 您是否检查过您的地址实际上适合为 16 位?微控制器通常没有 MMU,而是固定的内存映射。您应该查阅您的微控制器数据表/架构手册,以验证简单的模数减少到 16 位对于地址是可逆的。如果不是,最好声明一个大的全局数组并在其中使用 16 位索引,如果您真的非常渴望更小的指针。
  • ptr3: .short (str3-str0) 这是一个相对偏移量而不是地址,所以不是指针不能用于直接处理任何有用的东西,需要一个(32 位)基地址才有用。 .base+ptr3 可以是地址,但 ptr3 不能。

标签: string pointers gcc arm 16-bit


【解决方案1】:

在您的汇编语言文件中将ptr0str0 声明为.global。然后在C

extern unsigned short ptr0[] ;
extern const char str0[] ;
const char* getStringFromTable (unsigned char index)
  {
  return &str0[ptr0[index]] ;
  }

只要str0 表的总大小小于 64K,此方法就有效。

【讨论】:

  • 它在我调用时工作:ptr = getStringFromTable (1) 但当我写时:ptr = getStringFromTable (&amp;ptr4-&amp;ptr0) 编译器生成减去两个 32 位常量的代码:&ptr4 和 &ptr0,而不是简单地将一个字节加载到处理器的寄存器 R0
  • 从现在开始你就靠自己了,@Miroslaw。
  • 你知道如何强制编译器计算:&ptr4-&ptr0 在编译阶段,而不是在生成的代码中?
  • @Miroslaw:你到底为什么需要那个?
  • 看我的第一篇文章:'ptr = getStringFromTable(2)' 只生成两条处理器指令,而getStringFromTable(&ptr4-&ptr0) - 六条指令。
【解决方案2】:

指针是一个地址,arm 中的地址不能是 16 位,这是没有意义的,除了基于 Acorn 的 arm(如果我没记错的话,是 24 位),地址最小为 32 位(对于 arm)并且进入 aarch64 更大但永远不会变小。

这个

ptr3:   .short (str3-str0)

不会产生地址(因此它不能是指针)它会产生一个偏移量,该偏移量仅在您将其添加到基地址 str0 时才可用。

您无法生成 16 位地址(在已调试/可用的 arm 编译器中),但由于这里的所有内容似乎都是静态的(const/rodata),这使得它更容易解决、可解决的运行时,但更简单的预计算根据目前提供的信息。

const unsigned char str1[] ="First string";
const unsigned char str2[] ="Second string";
const unsigned char str3[] ="Third string";

蛮力大约需要 30 行代码来生成下面的头文件,如果您尝试压缩它,则更少,尽管临时程序不需要漂亮。

此输出故意较长以演示解决方案(并能够直观地检查工具),但编译器并不关心(因此最好使其较长且冗长以实现可读性/验证目的):

mystrings.h

const unsigned char strs[39]=
{
  0x46, //  0 F
  0x69, //  1 i
  0x72, //  2 r
  0x73, //  3 s
  0x74, //  4 t
  0x20, //  5  
  0x73, //  6 s
  0x74, //  7 t
  0x72, //  8 r
  0x69, //  9 i
  0x6E, // 10 n
  0x67, // 11 g
  0x00, // 12 
  0x53, // 13 S
  0x65, // 14 e
  0x63, // 15 c
  0x6F, // 16 o
  0x6E, // 17 n
  0x64, // 18 d
  0x20, // 19  
  0x73, // 20 s
  0x74, // 21 t
  0x72, // 22 r
  0x69, // 23 i
  0x6E, // 24 n
  0x00, // 25 
  0x54, // 26 T
  0x68, // 27 h
  0x69, // 28 i
  0x72, // 29 r
  0x64, // 30 d
  0x20, // 31  
  0x73, // 32 s
  0x74, // 33 t
  0x72, // 34 r
  0x69, // 35 i
  0x6E, // 36 n
  0x67, // 37 g
  0x00, // 38 
};
const unsigned short ptrs[3]=
{
  0x0000 //  0   0
  0x000D //  1  13
  0x001A //  2  26
};

然后编译器会在您使用它时处理所有地址生成

&strs[ptrs[n]]

根据您编写工具的方式,甚至可以有类似的东西

#define FIRST_STRING 0
#define SECOND_STRING 1

等等,以便您的代码可以找到字符串

strs[ptrs[SECOND_STRING]]

使程序更具可读性。所有自动生成的工具都是为您执行此偏移的临时工具。

该工具的 main() 部分可能看起来像

add_string(FIRST_STRING,"First string");
add_string(SECOND_STRING,"Second string");
add_string(THIRD_STRING,"Third string");

使用该函数和更多代码来转储结果。

然后您只需包含生成的输出并使用

strs[ptrs[THIRD_STRING]] 

在实际应用程序中键入语法。

为了沿着你开始的道路继续前进,如果这是你喜欢的(看起来需要更多的工作,但编码速度仍然很快)。

ptr0:
ptr3:   .short (str3-str0)
ptr4:   .short (str4-str0)

str0:
str3:   .asciz  "3-th string"
str4:   .asciz  "4-th string"

然后您需要导出 str0 和 ptr3、ptr4(根据需要根据您的汇编器的汇编语言),然后将它们作为指向 str0+ptr3 的指针访问

extern unsigned int str0;
extern unsigned short ptr3;
...
... *((unsigned char *)(str0+ptr3))

修复我有意或无意添加到该伪代码中的任何语法错误。

这也可以,您将拥有一个基地址,然后是该地址的数百个 16 位偏移量。

甚至可以做一些味道

const unsigned short ptrs[]={ptr0,ptr1,ptr2,ptr3};
...
(unsigned char *)(str0+ptrs[n])

使用某种 C 语法风格来创建该数组,但可能不值得付出额外的努力......

到目前为止,我们中的一些人提到的解决方案(上面展示了一个示例)(16 位偏移量,它们不是地址,这意味着不是指针)更容易编码、维护和使用,并且可能根据您的实现来读取。然而,实现它需要一个全尺寸的基地址和偏移量。不用专用工具也可以用 C 语言编写此代码,但专用工具实际上只需要几分钟的时间来编写。

我几乎每天都在编写程序来编写程序或程序来压缩/处理数据,为什么不呢。压缩就是一个很好的例子,想要将黑白图像嵌入到资源有限的 mcu 闪存中?不要将所有像素放入二进制文件中,从运行长度编码开始,然后从那里开始,这意味着由您编写的第三方工具将真实数据转换为适合的结构,这里是第三方为应用程序准备/压缩数据的工具。这个问题实际上只是另一种压缩算法,因为您试图减少数据量而不丢失任何数据。

还要注意这些字符串是什么,如果可能有重复或分数,该工具可能会更智能:

const unsigned char str1[] ="First string";
const unsigned char str2[] ="Second string";
const unsigned char str3[] ="Third string";
const unsigned char str4[] ="string";
const unsigned char str5[] ="Third string";

创造

const unsigned char strs[39]=
{
  0x46, //  0 F
  0x69, //  1 i
  0x72, //  2 r
  0x73, //  3 s
  0x74, //  4 t
  0x20, //  5  
  0x73, //  6 s
  0x74, //  7 t
  0x72, //  8 r
  0x69, //  9 i
  0x6E, // 10 n
  0x67, // 11 g
  0x00, // 12 
  0x53, // 13 S
  0x65, // 14 e
  0x63, // 15 c
  0x6F, // 16 o
  0x6E, // 17 n
  0x64, // 18 d
  0x20, // 19  
  0x73, // 20 s
  0x74, // 21 t
  0x72, // 22 r
  0x69, // 23 i
  0x6E, // 24 n
  0x00, // 25 
  0x54, // 26 T
  0x68, // 27 h
  0x69, // 28 i
  0x72, // 29 r
  0x64, // 30 d
  0x20, // 31  
  0x73, // 32 s
  0x74, // 33 t
  0x72, // 34 r
  0x69, // 35 i
  0x6E, // 36 n
  0x67, // 37 g
  0x00, // 38 
};
const unsigned short ptrs[5]=
{
  0x0000 //  0   0
  0x000D //  1  13
  0x001A //  2  26
  0x0006 //  3   6
  0x001A //  4  26
};

【讨论】:

  • 天哪!您的 500 行解决方案是否提供了我的 6 行解决方案所没有的任何功能?
  • @TonyK 是的,我填写了数据(这使得答案的大小很容易成倍增加)它总体上是一个非常简短的答案,但同时我们都在说同样的话,不要使用偏移量一个地址。这并没有下沉,所以可能需要更多的话。
  • 如果你坚持!
  • 我仍然没有在汇编中找到解决方案,因为我不知道如何从汇编文件中告诉编译器 &ptr4-&ptr0 = 2 (const)。所以我会尝试你的解决方案,@old_timer。非常感谢。
猜你喜欢
  • 2015-12-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-10-14
  • 1970-01-01
  • 2021-02-01
  • 1970-01-01
  • 2021-12-03
相关资源
最近更新 更多