将易失性数组与非易失性数组进行比较

Posted

技术标签:

【中文标题】将易失性数组与非易失性数组进行比较【英文标题】:Comparing a volatile array to a non-volatile array 【发布时间】:2015-02-22 16:36:08 【问题描述】:

最近我需要比较两个 uint 数组(一个是易失性数组,另一个是非易失性数组),结果令人困惑,一定是我对易失性数组有误解。

在将该数组与全局易失性数组进行比较之前,我需要从输入设备读取一个数组并将其写入一个局部变量。如果有任何区别,我需要将新数组复制到全局数组并将新数组发布到其他平台。代码是打击:

#define ARRAYLENGTH 30
volatile uint8 myArray[ARRAYLENGTH];

void myFunc(void)
    uint8 shadow_array[ARRAYLENGTH],change=0;
    readInput(shadow_array);
    for(int i=0;i<ARRAYLENGTH;i++)
        if(myArray[i] != shadow_array[i])
            change = 1;
            myArray[i] = shadow_array[i];
            
        
    if(change)
        char arrayStr[ARRAYLENGTH*4];
        array2String(arrayStr,myArray);
        publish(arrayStr);
        
    

但是,这不起作用,每次 myFunc 运行时,都会发布一条新消息,大部分与之前的消息相同。

所以我在代码中插入了一个日志行:

for(int i=0;i<ARRAYLENGTH;i++)
    if(myArray[i] != shadow_array[i])
        change = 1;
        log("old:%d,new:%d\r\n",myArray[i],shadow_array[i]);
        myArray[i] = shadow_array[i];
        
    

我得到的日志如下:

old:0,new:0
old:8,new:8
old:87,new:87
...

由于解决错误时间紧迫,我解决了如下问题:

char arrayStr[ARRAYLENGTH*4];
char arrayStr1[ARRAYLENGTH*4];
array2String(arrayStr,myArray);
array2String(arrayStr1,shadow_array);
if(strCompare(arrayStr,arrayStr1))
    publish(arrayStr1);
    

但是,这种方法远非有效。如果有人有合理的解释,我想听听。

谢谢。


[从 cmets 更新:]

对于 volatile 部分,全局数组必须是 volatile,因为其他线程正在访问它。

【问题讨论】:

对于两个第一个 sn-ps:您确定向我们展示了产生观察到的行为的确切代码吗? readInput的定义是什么?用 -Wall 编译,有没有警告?为什么数组易失?真的可以改变吗? 正如我所说,它不是确切的代码,只是等效的。在原始代码中,我从 uart 接口逐字节读取数组,所以我不想让 sn-ps 真的很长。但比较步骤与原始代码中的步骤相同,只是数组名称不同。 @rici readInput 通过 uart 发送消息并将接收到的字节放入 shadow_array,此外它还检查校验和。但它工作正常。对于 volatile 部分,全局数组必须是 volatile,因为其他线程正在访问它。并且 shadow_array 必须是非易失性的,因为 readInput 方法不接受易失性数组作为参数。 你的真实代码可能看起来像这样:if (!(myArray[i] = shadow_array[i])) change = 1; myArray[i] = shadow_array[i]; 【参考方案1】:

如果全局数组是易失的,您的跟踪代码可能不准确:

for(int i=0;i<ARRAYLENGTH;i++)
    if(myArray[i] != shadow_array[i])
        change = 1;
        log("old:%d,new:%d\r\n",myArray[i],shadow_array[i]);
        myArray[i] = shadow_array[i];
        
    

问题在于比较行读取了一次myArray[i],但日志消息又读取了一次,并且由于它是易失性的,因此不能保证两次读取会给出相同的值。一种准确的日志记录技术是:

for (int i = 0; i < ARRAYLENGTH; i++)

    uintu_t value;
    if ((value = myArray[i]) != shadow_array[i])
    
        change = 1;
        log("old:%d,new:%d\r\n", value, shadow_array[i]);
        myArray[i] = shadow_array[i];
    

这会复制比较中使用的值并报告该值。我的直觉是它不会显示出差异,但理论上它可以。

【讨论】:

实际上,只是为了测试两次读取之间是否发生任何事情,我们在您插入“值”的位置插入了两个变量,但它仍在推送更新:这是屏幕截图:prntscr.com/68hcrf 我不确定myArray 到底有多不稳定;对于我作为可能原因显示为实际原因的内容,它必须相当特别不稳定。这很奇怪,因为当你第二次阅读它时,它已经给出了你想要的答案,但第一次没有得到。你在搞乱 UART 之类的东西,你在 cmets 中说,所以可能是你的编译器没有按照你的要求去做。不过,您可能必须查看生成的汇编程序才能知道这一点。存储数组的内存是否有时序属性?你用的是什么平台? 屏幕截图中的代码是for (ii = 0; ii &lt; ARRAYLENGTH; ii++) uint8_t a_ = myArray[ii]; uint8_t b_ = shadow_array[ii]; if (a_ != b_) change = 1; printf("old:%d,new:%d\r\n", a_, b_); myArray[ii] = shadow_array[ii]; ,其中没有显示第二个右大括号。清楚显示的代码不应生成新旧相同的行。除了“编译器错误”之外,我没有解释为什么会这样做,但您不太可能第一次在这里遇到问题。 我们也无法给出合理的解释。然后我想可能有一些我们不知道的关于波动性的东西。让我们等待其他人是否有任何解释。如果不是,则可能是由于编译器或优化级别的原因。顺便说一句,大括号已完成,我在截图时忘记包含它。【参考方案2】:

全局数组必须是可变的,因为其他线程正在访问它

正如您“很好地”观察到的那样,声明一个数组 volatile 并不是保护它免受不同线程并发读/写访问的方法。

为此使用互斥锁。例如,通过将对“全局数组”的访问包装到一个锁定和解锁该互斥锁的函数中。那就只能用这个函数访问“全局数组”了。

参考资料:

Why is volatile not considered useful in multithreaded C or C++ programming? https://www.kernel.org/doc/Documentation/volatile-considered-harmful.txt

对于printf()ing unsigned 整数也使用转换说明符u 而不是d

【讨论】:

我目前正在度假,我会尝试在我回来时为访问全局数组的函数插入一个互斥锁。我希望它有效。但是,老实说,我并不那么乐观,因为即使我将代码更改为以下内容,也没有任何解释为什么会发生这种情况:prntscr.com/68hcrf【参考方案3】:

当变量(或数组)可能在当前程序执行流程之外发生变化时,应将其声明为 volatile。这可能由并发线程或 ISR 发生。 但是,如果只有一个实际写入它的人,而所有其他人都是 jsut Readers,那么实际编写的代码可能会将其视为非易失性(即使没有办法告诉编译器这样做)。

因此,如果比较函数是项目中全局数组实际更改(更新)的唯一点,那么多次读取就没有问题。尽管有 volatile 声明,但可以在(外部)知识的情况下设计代码,即外部源不会发生任何更改。

然而,“读者”确实知道变量(或数组内容)可能会改变并且不会缓冲它读取的内容(例如,通过将读取的值存储在寄存器中以供进一步使用),但仍然是数组内容他们在阅读时可能会发生变化,并且整个信息可能会不一致。 所以建议使用互斥锁是个好主意。 但是,即使没有人从外部弄乱数组,比较循环失败的原始问题也无济于事。

另外,我想知道如果 myArray 仅在本地使用并且发布是通过发送一个指向 ArrayStr 的指针(它是一个指向非易失性字符(数组)的指针来完成),我想知道为什么它被声明为 volatile。 没有理由 myArray 应该是易变的。实际上,它的存在根本没有理由: 只需读入数据,创建一个临时字符串,如果它与原始字符串不同,则替换旧字符串并发布。好吧,总是构建字符串可能效率较低,但它使代码更短并且显然有效。

static char arrayStr[ARRAYLENGTH*4]=;
char tempStr[ARRAYLENGTH*4];
array2String(tempStr,shadow_array);
if(strCompare(arrayStr,tempStr))
    strCopy(arrayStr, tempStr);
    publish(arrayStr);
    

【讨论】:

以上是关于将易失性数组与非易失性数组进行比较的主要内容,如果未能解决你的问题,请参考以下文章

易失性变量和非易失性重新排序/可见性

MicroPython ESP32NVS数据非易失性存储示例讲解说明

自动重新评估非易失性 UDF

everspin非易失性存储器中MRAM的潜在用途

展望由非易失性设备构成的未来存储

ESP32学习笔记(23)——NVS(非易失性存储)接口使用