【问题标题】:C word pointer to a byte structure member指向字节结构成员的 C 字指针
【发布时间】:2018-05-10 21:09:48
【问题描述】:

Edit2 我使用的是 foobar(*bar);在示例中,而不是 *myvar,很尴尬。

编辑这似乎是 MSP430 微控制器的德克萨斯编译器的行为,而不是“c”的东西。澄清一下,下面的代码是使用德州 LTS 编译器 16.9.x 为 MSP430FR5969 16 位(20 位寻址)微控制器使用 code composer studio 7 编译的。结果与 STS 17.9.x 相同。我编辑了代码示例以使其更加明确,这不是命名的事情。除非您明确要求,否则微控制器编译器不会在字边界上对齐结构成员。

我有这样的结构:

struct foo_{
   uint8_t low;
   uint8_t high;
} foo;

而我正试图变得像这样聪明:

void bar(){
   uint16_t * volatile myvar;
   myvar = (uint16_t *) &foo.low;
   foobar(*myvar);
}

void foobar(uint16_t value){
   printf("Value %d\n", value);
}

您希望指针 *myvar 指向 foo.low 地址,但它完全不同。如果我们将可移植性抛到窗外,它在 C 中不应该完全有效吗?

编辑 我已经编辑了代码示例。意外行为是调试器(能够显示来自奇数内存位置的字数据)和实际处理器(将从奇数地址读取字时返回 0xFFFF)中显示的内容不匹配。编译代码有时会失败,有时不是由结构在 FRAM 内存中的实际位置引起的。

【问题讨论】:

  • 根据我对结构的理解,这是未定义的行为,因为可以填充结构,也许检查 sizeof(foo) 并查看它是否不是 2
  • 你确定它是完全不同的吗?如果您查看十六进制的值(预期值和实际值),您是否看到 any 关系?而且由于放弃了可移植性会导致结果不正确,也许你根本就没有那么聪明。
  • *bar 不是指针。请澄清您的问题,最好制作一个 MCVE (minimal reproducible example)。
  • 高度依赖于编译器和目标机器。 Struct 并不总是紧密包装(可以填充),大部分时间都对齐到机器字边界以便于访问。
  • 这违反了严格的别名,你应该使用union。除此之外,bar 似乎应该 指向&foo.low,但没有minimal reproducible example 就无法判断。

标签: c pointers type-conversion


【解决方案1】:

编辑 该行为是由存储在奇数内存位置的字节地址引起的。 MSP430 微控制器无法从奇数地址返回字值,并将 0xFFFF 分配给目标变量。 一些示例/测试用例有效,因为结构成员恰好位于偶数地址中。

uint8_t myconfig_rw.current_l 位于(在此时编译的代码中)内存地址0x04563,不是字对齐的。这 当前 = (USHORT *) &myconfig_rw.current_l;行生成这个:

MOVX.A #0x04563,0x0000a(SP)

当我将其遵从单词变量时,会生成这些行

MOVA 0x000a(SP),R15
MOV.W @R15,0x000e(SP)

0x000e 收到 0xFFFF 而不是地址 0x04563 中的任何内容,因为处理器无法从奇数地址读取字。

现在代码编写器调试器在显示奇数地址的字值方面没有问题,并且会在指针地址中愉快地显示预期值(0x140)。

要解决此问题,需要手动排列两个字节的值以从偶数地址开始,或者在字和字节之间进行手动转换。使用联合不是解决方案,因为注册表是外部突发读/写可寻址的。寄存器地址可以移动一个字节,具体取决于是否需要填充。

部分解决方法是将注册表结构定义为从字对齐地址开始并记录代码以希望“下一个人”不会弄乱它。将一个单词转换为两个字节,反之亦然似乎更安全。

这似乎是 Code Composer Studio 特有的行为,它使用 Texas TLV 17.x 编译器为 MSP430FR5969 16 位微控制器生成代码。如果不是很好,问题中的代码 sn-p 似乎是“有效的”。在实际应用中使用时,结果是不可预测的。考虑到这个 sn-p:

struct myconfig_rw_{
    uint8_t current_l;
    uint8_t current_h;
} myconfig_rw;

void PWM_Decode_Init(){

USHORT * volatile current;
current = (USHORT *) &myconfig_rw.current_l;
PWM_status.current_constant=Calculate_Current_Constant(*current, false);
}

*current 实际上将包含来自 myconfig_rw.current_l 和 .current_h 的正确 16 位值。但是传递给 Calculate_Current_Constant 函数的值不正确,函数接收到的值是 0xFFFF。

作为结论,你不应该为了自己的利益而试图太聪明地使用指针。

一个独立的小例子,将提供预期的结果:

#include <msp430.h> 
#include <stdint.h>
#include <stdio.h>

struct foo_{
    uint8_t low;
    uint8_t high;
}foo;

/**
 * main.c
 */
void foobar(uint16_t test);

int main(void)
{
    uint16_t * volatile bar;
    WDTCTL = WDTPW | WDTHOLD;   // stop watchdog timer
    foo.low=0x10;
    foo.high=0xBB;
    bar = (uint16_t *) &foo.low;
    printf("address %p, value %x\n", bar, *bar);
    foobar(bar);
    __no_operation();
    return 0;
}

void foobar(uint16_t test){
    printf("value %x\n", test );
}

【讨论】:

  • 感谢您解释发生了什么。我相信我遇到了与几年前非常相似的事情。这不是完全相同的问题,但确实与奇数/偶数地​​址有关。我不认为编译器生成的代码会给出不正确的结果,但性能会受到影响。后来我找到了解释,需要确保指针在均匀的边界上。
  • @dernst 在 texas 编译器中,您可以使用 #pragma DATA_ALIGN 在偶数地址上启动结构,这样您就可以通过排列成员来确保给定的低字节成员位于偶数地址上。在这种情况下,我认为谨慎是更好的部分,并审查了代码以确保将外部字访问分解为字节操作。
  • @dernst 最终我决定用字节和字成员来实现它——处理分成字节的 16 位有符号值变得太麻烦了。您仍然可以从外部访问高/低字节,因为它是由偏移量寻址的,这与基于名称的内部操作不同。您必须照看注册表以确保您的单词变量是单词对齐的,但这就是它们的中断。
【解决方案2】:

我认为 Groo 很可能是对的。下面是按预期工作的代码。我没有在问题中看到地址是如何打印出来的。如果你把 printf 语句放在 main 而不是 bar 中,它会编译并运行得很好,但会给你带来误导性的结果。

#include <stdint.h>
#include <stdio.h>

struct _foo{
    uint8_t low;
    uint8_t high;
} foo;

void bar(){
    uint16_t *bar;
    bar = (uint16_t *) &foo.low;
    printf("From within foo() : foo.low addr: %x, foo.high addr: %x, bar addr: %x\n", (uint32_t) &foo.low, (uint32_t)&foo.high, (uint32_t)bar);

}

main(){
    bar();
    printf("From within main(): foo.low addr: %x, foo.high addr: %x, bar addr: %x\n", (uint32_t) &foo.low, (uint32_t)&foo.high, (uint32_t)bar);
}

【讨论】:

  • 是的,我的简单示例代码确实可以按您的预期工作,只要您记得 volatile 指针定义是“uint16_t * volatile foo”,而不是“volatile uint16_t * foo”。请参阅我的答案以获取编译代码。然而,来自实际应用程序的代码 sn-p 确实 工作,因此在一个非平凡的程序中,你不应该期望这种 hack 产生你想要的结果。如果我这样做: *(uint16_t *) &foo.low;结果我会得到 0xFFFF ..
  • 那么您认为发生了什么?当您使用 volatile 关键字影响编译器时,代码是否在优化关闭的情况下工作?
  • volatile 仅用于调试器中的所见即所得。我已经向德克萨斯州提出了一个案例。我很确定这是一个“功能”。如果我将地址存储为变量,请将其传递给函数并将其转换回其中的指针,它会按预期工作。但这不应该是完全必要的。在你问之前,不,没有真正的理由将 2 字节值转换为 16 位指针,因为函数在任何情况下都只能获取值。有些地方低级函数必须直接操作该配置结构,我想,这不是很好。
  • 关闭所有优化并不是一个真正的选择。它是一个 16MHz 微控制器,甚至没有寄存器优化(最低级别),它变得非常缓慢。
  • 我同意这可能是德州编译器的问题,因为您说它通过变量传递而不是在将其作为值传递之前取消引用来工作。我很想听听德克萨斯要说什么!
猜你喜欢
  • 2023-03-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-03-24
  • 2013-05-02
  • 2021-12-01
相关资源
最近更新 更多