C 重新定义定义

C 重新定义定义,c,casting,preprocessor,C,Casting,Preprocessor,我在查看一些代码时偶然发现: 在头文件中,我们定义了这个MAGIC_地址 #define ANOTHER_ADDRESS ((uint8_t*)0x40024000) #define MAGIC_ADDRESS (ANOTHER_ADDRESS + 4u) 然后在各种文件中贯穿代码,我们有如下内容: *uint32\u t*魔法地址=0; 和 *uint32*MAGIC\u ADDRESS=SOME\u OTHER\u DEFINE 这是编译的,显然是有效的,并且不会抛出任何linter错误。

我在查看一些代码时偶然发现:

在头文件中,我们定义了这个MAGIC_地址

#define ANOTHER_ADDRESS ((uint8_t*)0x40024000)
#define MAGIC_ADDRESS (ANOTHER_ADDRESS + 4u)
然后在各种文件中贯穿代码,我们有如下内容:

*uint32\u t*魔法地址=0; 和 *uint32*MAGIC\u ADDRESS=SOME\u OTHER\u DEFINE

这是编译的,显然是有效的,并且不会抛出任何linter错误。MAGIC_地址=0;如果没有演员阵容,就不能像我预期的那样编译

因此,我的问题是:

我们为什么要这么做,而不是一开始就做一件事? 这实际上是如何工作的?我以为预处理器的定义是不可触及的,我们如何才能铸造一个呢? 如果需要使用宏将值指定给内存中的特定位置 允许您以相对容易阅读的方式进行操作,如果您需要的话 稍后使用另一个地址-只需更改宏定义

宏由预处理器转换为值。当您取消引用时 它可以让你访问你可以读或写的内存。这没什么关系 处理预处理器用作标签的字符串

如果需要使用宏将值指定给内存中的特定位置 允许您以相对容易阅读的方式进行操作,如果您需要的话 稍后使用另一个地址-只需更改宏定义

宏由预处理器转换为值。当您取消引用时 它可以让你访问你可以读或写的内存。这没什么关系 处理预处理器用作标签的字符串


不是强制转换允许分配工作,而是*解引用操作符。宏将扩展为指针常量,不能重新指定常量。但由于它是一个指针,您可以将它指定给它所指向的内存。所以如果你写

*MAGIC_ADDRESS = 0;
你不会出错的


由于宏扩展为uint8_t*,因此必须将强制转换分配给该地址的一个4字节字段,而不仅仅是一个字节。将其强制转换为uint32\u t*使其成为4字节赋值。

不是强制转换允许赋值工作,而是*解引用运算符。宏将扩展为指针常量,不能重新指定常量。但由于它是一个指针,您可以将它指定给它所指向的内存。所以如果你写

*MAGIC_ADDRESS = 0;
你不会出错的

由于宏扩展为uint8_t*,因此必须将强制转换分配给该地址的一个4字节字段,而不仅仅是一个字节。将其强制转换为uint32_t*使其成为一个4字节的赋值

我们为什么要这么做,而不是一开始就做一件事

这是一个公平的问题。一种可能性是,另一个_地址用作多种数据的基址,但给出的代码片段没有说明为什么不应将另一个_地址定义为扩展为uint32_t*类型的表达式。但是,请注意,如果进行了更改,则需要将MAGIC_地址的定义更改为另一个_地址+1u

这实际上是如何工作的?我以为预处理器的定义是不可触及的,我们如何才能铸造一个呢

如果C源代码中出现范围内宏标识符,则替换宏的替换文本。简化一点,如果替换文本也包含宏标识符,那么这些宏标识符将被替换为它们的替换文本,等等。。从本质上讲,代码片段中没有宏被强制转换,但是完全扩展的结果表达了一些强制转换

例如,这个

*(uint32_t*)MAGIC_ADDRESS = 0;
。。。扩展到

*(uint32_t*)(ANOTHER_ADDRESS + 4u) = 0;
。。。然后继续

*(uint32_t*)(((uint8_t*)0x40024000) + 4u) = 0;
。那里没有宏的强制转换,但存在宏替换文本的有效强制转换

#define ANOTHER_ADDRESS ((uint8_t*)0x40024000)
#define MAGIC_ADDRESS (ANOTHER_ADDRESS + 4u)
我们为什么要这么做,而不是一开始就做一件事

这是一个公平的问题。一种可能性是,另一个_地址用作多种数据的基址,但给出的代码片段没有说明为什么不应将另一个_地址定义为扩展为uint32_t*类型的表达式。但是,请注意,如果进行了更改,则需要将MAGIC_地址的定义更改为另一个_地址+1u

这实际上是如何工作的?我以为预处理器的定义是不可触及的,我们如何才能铸造一个呢

如果C源代码中出现范围内宏标识符,则替换宏的替换文本。简化一点,如果替换文本也包含宏标识符,那么这些宏标识符将被替换为它们的替换文本,等等。。从本质上讲,代码片段中没有宏被强制转换,但是完全扩展的结果表达了一些强制转换

例如,这个

*(uint32_t*)MAGIC_ADDRESS = 0;
。。。扩展到

*(uint32_t*)(ANOTHER_ADDRESS + 4u) = 0;
。。。然后继续

*(uint32_t*)(((uint8_t*)0x40024000) + 4u) = 0;
。那里没有宏的强制转换,但存在宏替换文本的有效强制转换

#define ANOTHER_ADDRESS ((uint8_t*)0x40024000)
#define MAGIC_ADDRESS (ANOTHER_ADDRESS + 4u)
然后整个 各种文件中的代码都是这样的:

这就是问题所在——你不想在整个过程中重复任何东西。相反,这是或多或少惯用的嵌入式C代码的样子:

// Portable to compilers without void* arithmetic extension
#define BASE_ADDRESS ((uint8_t*)0x40024000)
#define REGISTER1 (*(uint32_t*)(ANOTHER_ADDRESS + 4u))
然后,您可以写入REGISTER1=42,或者如果REGISTER1!=42等。如您所想,这通常用于存储映射的外围控制寄存器

#define ANOTHER_POINTER ((volatile uint8_t*)0x40024000)
#define MAGIC_APOINTER (ANOTHER_ADDRESS + 4u)
如果您使用的是gcc或clang,那么作为扩展,还有另一层类型安全性:您不希望编译器允许*BASE_ADDRESS进行编译,因为您可能只想访问寄存器,*BASE_ADDRESS表达式不应该通过代码审查。因此:

// gcc, clang, icc, and many others but not MSVC
#define BASE_ADDRESS ((void*)0x40024000)
#define REGISTER1 (*(uint32_t*)(ANOTHER_ADDRESS + 4u))
void*上的算术是大多数非Microsoft编译器采用的gcc扩展,它很方便:*BASE_地址表达式不会编译,这是一件好事

我假设BASE_地址是STM32 MCU上电池支持的RAM的地址,在这种情况下,寄存器解释是不正确的,因为您只想保存一些应用程序数据,并且您使用的是C语言,而不是汇编语言,还有一个我们称之为结构的方便的东西——绝对要使用一个结构,而不是这个丑陋的黑客。非易失性区域中存储的东西不是寄存器,它们只是结构中的字段,结构本身以非易失性方式存储:

#define BKPSRAM_BASE_ ((void*)0x40024000)
#define nvstate (*(NVState*)BKPSRAM_BASE_)

enum NVLayout { NVVER_1 = 1, NVVER_2 = 2 };

struct {
  // Note: This structure is persisted in NVRAM.
  // Do not reorder the fields.
  enum NVLayout layout;
  // NVVER_1 fields
  uint32_t value1;
  uint32_t value2;
  ...
  /* sometime later after a release */
  // NVVER_2 fields
  uint32_t valueA;
  uint32_t valueB;
} typedef NVState;
使用:

现在我们来谈谈问题的症结所在:您的代码审查集中在细节上,但您也应该透露大局。如果我的总体猜测是正确的——这一切都是关于在电池支持的RAM中粘贴一些数据,那么应该使用实际的数据结构,而不是宏黑客和手动偏移管理。恶心

是的,为了向前兼容,您需要这个布局字段,除非整个NVRAM区域预先初始化为零,并且您可以使用零作为默认值

这种方法很容易让您复制NVRAM状态,例如,如果您想通过导线将其发送用于诊断目的-您不必担心有多少数据,只需使用sizeofNVState将其传递给函数(如fwrite),您甚至可以使用该NV数据的工作副本-无需单个memcpy:

NVState wkstate = nvstate;
/* user manipulates the state here */
if (OK_pressed)
  nvstate = wkstate;
else if (Cancel_pressed)
  wkstate = nvstate;
然后在各种文件中贯穿代码,我们有如下内容:

这就是问题所在——你不想在整个过程中重复任何东西。相反,这是或多或少惯用的嵌入式C代码的样子:

// Portable to compilers without void* arithmetic extension
#define BASE_ADDRESS ((uint8_t*)0x40024000)
#define REGISTER1 (*(uint32_t*)(ANOTHER_ADDRESS + 4u))
然后,您可以写入REGISTER1=42,或者如果REGISTER1!=42等。如您所想,这通常用于存储映射的外围控制寄存器

#define ANOTHER_POINTER ((volatile uint8_t*)0x40024000)
#define MAGIC_APOINTER (ANOTHER_ADDRESS + 4u)
如果您使用的是gcc或clang,那么作为扩展,还有另一层类型安全性:您不希望编译器允许*BASE_ADDRESS进行编译,因为您可能只想访问寄存器,*BASE_ADDRESS表达式不应该通过代码审查。因此:

// gcc, clang, icc, and many others but not MSVC
#define BASE_ADDRESS ((void*)0x40024000)
#define REGISTER1 (*(uint32_t*)(ANOTHER_ADDRESS + 4u))
void*上的算术是大多数非Microsoft编译器采用的gcc扩展,它很方便:*BASE_地址表达式不会编译,这是一件好事

我假设BASE_地址是STM32 MCU上电池支持的RAM的地址,在这种情况下,寄存器解释是不正确的,因为您只想保存一些应用程序数据,并且您使用的是C语言,而不是汇编语言,还有一个我们称之为结构的方便的东西——绝对要使用一个结构,而不是这个丑陋的黑客。非易失性区域中存储的东西不是寄存器,它们只是结构中的字段,结构本身以非易失性方式存储:

#define BKPSRAM_BASE_ ((void*)0x40024000)
#define nvstate (*(NVState*)BKPSRAM_BASE_)

enum NVLayout { NVVER_1 = 1, NVVER_2 = 2 };

struct {
  // Note: This structure is persisted in NVRAM.
  // Do not reorder the fields.
  enum NVLayout layout;
  // NVVER_1 fields
  uint32_t value1;
  uint32_t value2;
  ...
  /* sometime later after a release */
  // NVVER_2 fields
  uint32_t valueA;
  uint32_t valueB;
} typedef NVState;
使用:

现在我们来谈谈问题的症结所在:您的代码审查集中在细节上,但您也应该透露大局。如果我的总体猜测是正确的——这一切都是关于在电池支持的RAM中粘贴一些数据,那么应该使用实际的数据结构,而不是宏黑客和手动偏移管理。恶心

是的,为了向前兼容,您需要这个布局字段,除非整个NVRAM区域预先初始化为零,并且您可以使用零作为默认值

这种方法很容易让您复制NVRAM状态,例如,如果您想通过导线将其发送用于诊断目的-您不必担心有多少数据,只需使用sizeofNVState将其传递给函数(如fwrite),您甚至可以使用该NV数据的工作副本-无需单个memcpy:

NVState wkstate = nvstate;
/* user manipulates the state here */
if (OK_pressed)
  nvstate = wkstate;
else if (Cancel_pressed)
  wkstate = nvstate;

我担心这两个定义都是错误的,或者至少不是完全正确的

如果指针引用硬件寄存器,则应将其定义为指向易失性值的指针

#define ANOTHER_POINTER ((volatile uint8_t*)0x40024000)
#define MAGIC_APOINTER (ANOTHER_ADDRESS + 4u)

我被定义为uint8_t*指针,可能是因为作者希望指针运算在字节级完成。

我担心这两个定义都是错误的,或者至少不是完全正确的

如果指针 正在引用硬件寄存器

#define ANOTHER_POINTER ((volatile uint8_t*)0x40024000)
#define MAGIC_APOINTER (ANOTHER_ADDRESS + 4u)

我被定义为uint8_t*指针,可能是因为作者希望在字节级完成指针运算。

我不理解这个问题。它不是重新定义它,而是取消对它的引用并将其分配给指定地址的内存。强制转换是因为没有它,您无法将数字用作指针。尽管用例正在重新编译为不同的指针类型。MAGIC_地址=0;未编译,因为您删除了*和第二次强制转换。神奇地址可能对应于内存映射的硬件设备。请记住,宏在预处理过程中被替换。所以执行的代码是*uint32\u t*uint8\u t*0x40024000=SOME\u OTHER\u DEFINE;记住:在编译器看到代码之前,所有宏替换都在预处理器中完成。所以没有强制转换预处理器宏这样的事情。当真正理解强制转换的编译器(而不是预处理器)看到代码时,宏已经被替换了。我不理解这个问题。它不是重新定义它,而是取消对它的引用并将其分配给指定地址的内存。强制转换是因为没有它,您无法将数字用作指针。尽管用例正在重新编译为不同的指针类型。MAGIC_地址=0;未编译,因为您删除了*和第二次强制转换。神奇地址可能对应于内存映射的硬件设备。请记住,宏在预处理过程中被替换。所以执行的代码是*uint32\u t*uint8\u t*0x40024000=SOME\u OTHER\u DEFINE;记住:在编译器看到代码之前,所有宏替换都在预处理器中完成。所以没有强制转换预处理器宏这样的事情。当真正理解强制转换的编译器(而不是预处理器)看到代码时,宏已经被替换。