table 偏移量的 C 递归宏
C Recursive Macros for table offsets
我们需要为外部存储器创建静态 table 地址。
存储结构的大小是已知且固定的。但是,这些结构的地址必须从内存部分的开头开始。
我们打算通过使用宏来做到这一点,因为让 table 随着 table 的元素在编译时发生变化而变化。
实现看起来像这样:
#define BLOC (0xFF)
#define FINAL_ADR(N) (ADR_##N + SIZE_##N)
#define GET_ADR(N) (FINAL_ADR(N) + (BLOC - (FINAL_ADR(N) & BLOC)))
#define ADR_0 (0x0)
#define SIZE_0 (5)
#define ADR_1 GET_ADR(0)
#define SIZE_1 (300)
#define ADR_2 GET_ADR(1)
#define SIZE_2 (130)
...
const struct {
uint32_t addr, size;
} mem[] = {
{.addr = ADR_0, .size = SIZE_0},
{.addr = ADR_1, .size = SIZE_1},
{.addr = ADR_2, .size = SIZE_2},
};
上面的代码尝试根据元素的先前地址和大小分配一个地址。
- 元素 0 从位置 0x00 开始,占用 5 个字节(1 个部分)。
- 元素 1 应从位置 0xFF 开始并占用 300 字节(2 个部分)。
- 元素 2 应从位置 0x2FD 开始,占用 130 个字节(1 个部分)。
如果我们尝试实现上面的宏,编译器会失败。因为宏在元素 2 处引用了自身:
ADR_2 = ((GET_ADR(1)+ SIZE_1) + (0xFF- ((GET_ADR(1)+ SIZE_1) & 0xFF)))
是否可以绕过这个间接自引用宏?有没有更好的方法来完成这个?
您可以滥用枚举来获得它。
#include <stdint.h>
#define BLOC (0xFF)
#define FINAL_ADR(N) (ADR_##N + SIZE_##N)
#define GET_ADR(N) (FINAL_ADR(N) + (BLOC + 1 - (FINAL_ADR(N) & BLOC)))
enum {
ADR_0 = 0x0, SIZE_0 = 5,
ADR_1 = GET_ADR(0), SIZE_1 = 300,
ADR_2 = GET_ADR(1), SIZE_2 = 130,
};
const struct {
uint32_t addr, size;
} mem[] = {
{.addr = ADR_0, .size = SIZE_0},
{.addr = ADR_1, .size = SIZE_1},
{.addr = ADR_2, .size = SIZE_2},
};
编译为汇编程序源代码(x86_64):
.text
.section .rdata,"dr"
.globl mem # @mem
.p2align 4
mem:
.long 0 # 0x0
.long 5 # 0x5
.long 256 # 0x100
.long 300 # 0x12c
.long 768 # 0x300
.long 130 # 0x82
哦,您的阻塞公式需要额外的 + 1
才能达到 0x100 的下一个倍数。
这不是嵌入式系统中内存映射变量的方式。
所有具有自定义内存映射的变量都在链接描述文件中配置,而不是在源代码中。您在链接描述文件中创建段(有时也称为节),您可以在其中指定每个段的起始地址和大小。
然后在源代码中,您使用一些特定于编译器的非标准扩展在您创建的段内分配变量。 gcc 示例:
struct element0 stuff __attribute__ (section ("ELEMENT_0")) = { ... };
现在如果代码的其他部分需要知道这个变量的地址,那么可以用&stuff
.
获得
如果您的外部存储器是通过 SPI 等访问的串行存储器而不是内存映射,则只需使用(命名的)数字常量进行硬编码查找 table。不需要所有这些复杂的、不可读的宏。好的程序员写的代码越简单越好,而不是越复杂越好。
我们需要为外部存储器创建静态 table 地址。 存储结构的大小是已知且固定的。但是,这些结构的地址必须从内存部分的开头开始。 我们打算通过使用宏来做到这一点,因为让 table 随着 table 的元素在编译时发生变化而变化。
实现看起来像这样:
#define BLOC (0xFF)
#define FINAL_ADR(N) (ADR_##N + SIZE_##N)
#define GET_ADR(N) (FINAL_ADR(N) + (BLOC - (FINAL_ADR(N) & BLOC)))
#define ADR_0 (0x0)
#define SIZE_0 (5)
#define ADR_1 GET_ADR(0)
#define SIZE_1 (300)
#define ADR_2 GET_ADR(1)
#define SIZE_2 (130)
...
const struct {
uint32_t addr, size;
} mem[] = {
{.addr = ADR_0, .size = SIZE_0},
{.addr = ADR_1, .size = SIZE_1},
{.addr = ADR_2, .size = SIZE_2},
};
上面的代码尝试根据元素的先前地址和大小分配一个地址。
- 元素 0 从位置 0x00 开始,占用 5 个字节(1 个部分)。
- 元素 1 应从位置 0xFF 开始并占用 300 字节(2 个部分)。
- 元素 2 应从位置 0x2FD 开始,占用 130 个字节(1 个部分)。
如果我们尝试实现上面的宏,编译器会失败。因为宏在元素 2 处引用了自身:
ADR_2 = ((GET_ADR(1)+ SIZE_1) + (0xFF- ((GET_ADR(1)+ SIZE_1) & 0xFF)))
是否可以绕过这个间接自引用宏?有没有更好的方法来完成这个?
您可以滥用枚举来获得它。
#include <stdint.h>
#define BLOC (0xFF)
#define FINAL_ADR(N) (ADR_##N + SIZE_##N)
#define GET_ADR(N) (FINAL_ADR(N) + (BLOC + 1 - (FINAL_ADR(N) & BLOC)))
enum {
ADR_0 = 0x0, SIZE_0 = 5,
ADR_1 = GET_ADR(0), SIZE_1 = 300,
ADR_2 = GET_ADR(1), SIZE_2 = 130,
};
const struct {
uint32_t addr, size;
} mem[] = {
{.addr = ADR_0, .size = SIZE_0},
{.addr = ADR_1, .size = SIZE_1},
{.addr = ADR_2, .size = SIZE_2},
};
编译为汇编程序源代码(x86_64):
.text
.section .rdata,"dr"
.globl mem # @mem
.p2align 4
mem:
.long 0 # 0x0
.long 5 # 0x5
.long 256 # 0x100
.long 300 # 0x12c
.long 768 # 0x300
.long 130 # 0x82
哦,您的阻塞公式需要额外的 + 1
才能达到 0x100 的下一个倍数。
这不是嵌入式系统中内存映射变量的方式。
所有具有自定义内存映射的变量都在链接描述文件中配置,而不是在源代码中。您在链接描述文件中创建段(有时也称为节),您可以在其中指定每个段的起始地址和大小。
然后在源代码中,您使用一些特定于编译器的非标准扩展在您创建的段内分配变量。 gcc 示例:
struct element0 stuff __attribute__ (section ("ELEMENT_0")) = { ... };
现在如果代码的其他部分需要知道这个变量的地址,那么可以用&stuff
.
如果您的外部存储器是通过 SPI 等访问的串行存储器而不是内存映射,则只需使用(命名的)数字常量进行硬编码查找 table。不需要所有这些复杂的、不可读的宏。好的程序员写的代码越简单越好,而不是越复杂越好。