所以我有这个代码:
uint32_t s1 = 0xFFFFFFFFU;
uint32_t s2 = 0xFFFFFFFFU;
uint32_t v;
...
v = s1 * s2; /* Only need the low 32 bits of the result */
Run Code Online (Sandbox Code Playgroud)
在以下所有内容中,我假设编译器不能对s1或s2仅用于上述示例的初始化器的范围有任何先入之见.
如果我在一个整数大小为32位的编译器上编译它(例如编译x86时),没问题.编译器只是简单地使用s1和s2作为uint32_t类型化的值(不能进一步推广它们),并且乘法将简单地给出结果,如注释所示(模数UINT_MAX + 1在这种情况下为0x100000000).
但是,如果我在具有64位整数大小的编译器(例如x86-64)上编译它,则可能会从C标准中推断出未定义的行为.整数提升会看到uint32_t可以提升为int(64位有符号),然后乘法会尝试乘以2 int,如果它们恰好具有示例中显示的值,则会导致整数溢出,这是未定义的行为.
我对此是否正确,如果是这样,你会如何以理智的方式避免它?
我发现了这个类似的问题,但涵盖了C++:什么是最好的C++方式来模块化地安全地无符号整数?.在这里,我想得到一个适用于C的答案(最好是兼容C89).我不会考虑让一台糟糕的32位机器可能执行64位乘法,但这是一个可接受的答案(通常在代码中,这会引起关注,32位性能可能更为关键,因为通常那些机器速度较慢).
注意,当使用具有32位int大小的编译器编译时,同样的问题可以应用于16位无符号整数,或者当使用具有16位int大小的编译器编译时,同样的问题可以应用于无符号字符(后者可能与8位CPU的编译器相同) :C标准要求整数至少为16位,因此符合标准的编译器可能会受到影响).
我的一位同事遇到了编程ATMega的一些奇怪问题,这与访问输入输出端口有关.
在经过一些研究后观察问题,我得出结论,如果我们的目标是安全的C标准兼容软件,我们应该避免使用可能编译的指令SBI或CBI指令.我正在寻找这个决定是否正义,所以如果我的担忧是有效的.
Atmel处理器的数据表在这里,它是一个ATMega16.我将在下面参考本文档的一些页面.
我将使用WG14 N1256链接下本网站上的版本来参考C标准.
的SBI和CBI所述处理器的指令的位级只访问所讨论的位进行操作.因此,它们不是真正的读 - 修改 - 写(RMW)指令,因为据我所知,它们不执行读取(目标8位SFR).
在上面的数据表的第50页上,第一句开始就像所有AVR端口都具有真正的读 - 修改 - 写功能......,正在进行中它指定这仅适用于具有技术上不是RMW 的SBI和CBI指令的访问.数据表没有定义什么读取,例如PORTx寄存器应该返回(但它表明它们是可读的).所以我假设读取这些SFR是未定义的(它们可能返回写在它们上的最后一件事或当前输入状态或其他).
在页70,它列出了一些外部中断标志,这很有趣,因为这是SBI和CBI指令的性质变得重要的地方.发生中断时会设置标志,可以通过将它们写入一个来清除它们.因此,如果SBI是真正的RMW指令,它将清除所有三个标志,而不管操作码中指定的位.
现在让我们进入C的问题.
编译器本身是真正无关紧要的,唯一重要的事实是它可能在某些情况下使用CBI和SBI指令,我认为它使它不符合要求.
在上面提到的C99标准中,5.1.2.3节执行,第2节和第3节引用了这个(第13页)和6.7.3类型限定符,第6点(第109页).后者提到对具有volatile限定类型的对象的访问构成是实现定义的,但是在它之前的一些短语要求引用这样的对象的任何表达式都应该严格地根据抽象机器的规则进行评估.
另请注意,示例中使用的硬件端口volatile在相应的标头中声明.
例:
PORTA |= 1U << 6;
Run Code Online (Sandbox Code Playgroud)
众所周知,这可以转化为SBI.这意味着volatile(PORTA)对象上只发生Write访问.但是,如果有人写:
var = …Run Code Online (Sandbox Code Playgroud) 在某些体系结构上,可能需要为其他相同的对象提供不同的指针类型.特别是对于哈佛架构CPU,您可能需要以下内容:
uint8_t const ram* data1;
uint8_t const rom* data2;
Run Code Online (Sandbox Code Playgroud)
特别是这对于PICs的MPLAB C18(现已停产)中ROM/RAM指针的定义如何.它甚至可以定义如下内容:
char const rom* ram* ram strdptr;
Run Code Online (Sandbox Code Playgroud)
这意味着RAM中的指针指向RAM中指向ROM中的字符串的指针(使用ram不是必需的,因为默认情况下,这个编译器在RAM中,为了清楚起见,所有内容都添加了).
这种语法的好处是,当您尝试以不兼容的方式分配时,编译器能够提醒您,例如ROM位置的地址到指向RAM的指针(如此类似data1 = data2;,或者将ROM指针传递给函数)使用RAM指针会产生错误).
与此相反,在AVR-8的avr-gcc中,没有这种类型的安全性,因为它提供了访问ROM数据的功能.无法区分RAM指针和指向ROM的指针.
在某些情况下,这种类型的安全性对于捕获编程错误非常有益.
有没有办法以某种方式向指针添加类似的修饰符(例如通过预处理器,扩展到可以模仿这种行为的东西)来实现此目的?甚至是一些警告不正当访问的东西?(在avr-gcc的情况下,尝试在不使用ROM访问功能的情况下获取值)
有几个问题涉及这个问题的某些方面,但似乎都没有完全回答它.整个问题可归纳如下:
我的特定用例是一个解释器,我想让用户能够用解释器二进制文件和他提供的代码生成一个单独的文件可执行文件(解释器二进制文件是必须用它修补的可执行文件).用户提供的代码作为二进制数据).
类似的情况是自解压存档,其中程序(存档实用程序,如zip)能够构造这样的可执行文件,其中包含预构建的解压缩程序(已编译的可执行文件)和用户提供的数据(内容)存档).显然,此过程中不涉及编译器或链接器(感谢,Mathias为注释并指出7-zip).
使用现有问题,解决方案的特定路径如下所示:
将数据附加到exe - 这涉及向任意exes添加任意数据的方面,而不涉及如何实际访问它(基本上简单的追加通常有效,对于Unix的ELF格式也是如此).
在没有/ proc/self/exe的情况下查找当前可执行文件的路径 - 与上述相同,这将允许获取用于打开exe的文件名,以访问添加的数据.还有更多这类问题,但是它们都没有特别关注获得适合于实际将二进制文件作为文件打开的目的的路径问题(单独的目标可能(?)更容易实现 - 真的你不要甚至不需要路径,只是打开阅读的二进制文件.
除了填充二进制文件并打开文件以进行读取之外,还可能存在其他可能更优雅的方法.例如,可执行文件可以使得以后使用任意大小的数据对其进行修补变得相当简单,因此它它出现在某个正确的数据段"内"?(我真的找不到任何东西,对于固定大小的数据,它应该是微不足道的,尽管除非可执行文件有一些哈希值)
这可以做得相当好,与标准C的偏差尽可能小吗?甚至或多或少的跨平台?(至少从维护的角度来看)请注意,如果执行二进制数据添加的程序不依赖于编译器工具(用户可能没有),那么首选,但是需要这些工具的解决方案也可能有用. .
请注意已经编译的可执行标准(上面列表中的第一点),这需要一种完全不同于C/C++与GCC之类的问题中描述的解决方案:静态地将资源文件添加到可执行文件库或SDL嵌入图像内部的程序可执行文件中,要求嵌入数据编译时.
补充说明:
上面列出并在一些注释中提出的明显方法的问题,即只是附加到二进制文件并使用它,如下所示:
一个古老的想法,但从那以后我无法找到一些合理的方法来解决它引发的问题.所以我"发明"(见下文)非常紧凑,在我看来,性能相当好的PRNG,但我无法弄清楚算法在大位深度为它构建合适的种子值.我目前的解决方案只是暴力破解,它的运行时间是O(n ^ 3).
发电机
我的想法来自XOR水龙头(基本上是LFSR)一些用于发声的老式8bit机器.我摆弄了XOR作为C64的基础,尝试将操作码放在一起,并对结果有所了解.最终的工作解决方案如下所示:
asl
adc #num1
eor #num2
Run Code Online (Sandbox Code Playgroud)
这是6502上的5个字节.有一个精心选择的num1和num2,在累加器中它以看似随机的顺序迭代所有256个值,也就是说,当用于填充屏幕时它看起来相当随机(我写了一点256b演示回到这个).有40个合适的num1和num2对,所有这些都给出了不错的外观序列.
这个概念可以很好地推广,如果用纯C表示,它可能看起来像这样(BITS是序列的位深度):
r = (((r >> (BITS-1)) & 1U) + (r << 1) + num1) ^ num2;
r = r & ((1U<<BITS)-1U);
Run Code Online (Sandbox Code Playgroud)
这个C代码更长,因为它是通用的,即使使用无符号整数的全深度,C也没有必要的进位逻辑来将移位的高位传送到加法运算.
对于一些性能分析和比较,请在问题之后见下文.
问题/问题
生成器的核心问题是找到合适的num1和num2,这将使它迭代给定位深度的整个可能序列.在本节的最后,我附上了我的代码,这些代码只是暴力强制它.它将在合理的时间内完成多达12位,您可以等待所有16位(顺便说一下,有5736个可能的配对,前一次通过隔夜完全搜索获得),你可能会得到几个20位如果你有耐心 但O(n ^ 3)真的很讨厌......
(谁能找到第一个完整的32位序列?)
出现的其他有趣问题:
对于num1和num2,只有奇数值能够产生完整序列.为什么?这可能并不难(简单的逻辑,我猜),但我没有合理地证明它.
沿着num1(加值)有一个镜像属性,也就是说,如果给定'b'num2的'a'给出一个完整的序列,那么'a'的2个补码(在给定的位深度中)具有相同的num2也是一个完整的序列.我只是在我计算的所有完整世代中可靠地观察到这种情况.
第三个有趣的特性是,对于所有num1和num2对,得到的序列似乎形成适当的圆,也就是说,至少数字零似乎总是圆的一部分.如果没有这个属性,我的暴力搜索会在无限循环中死亡.
额外奖励:这个PRNG之前是否已经知道?(我刚刚重新发明了它)?
这是蛮力搜索的代码(C):
#define BITS 16
#include "stdio.h"
#include "stdlib.h"
int main(void)
{
unsigned int r;
unsigned int c;
unsigned int num1;
unsigned int num2;
unsigned int mc=0U;
num1=1U; /* Only odd add …Run Code Online (Sandbox Code Playgroud) 我最近开始使用 git,默认情况下它想使用 vim。我一直想在这个编辑器中更深入地研究,但总是被退回,只有在出于某种原因(例如在几乎死掉的 Linux 系统的情况下)不可避免时才使用它。即使现在我很快最终重新配置 git 以使用 mcedit (我用于编程)。
问题是,好吧,我发出了命令:help,看看如何开始。然后在帮助系统中,它建议我使用CTRL-]. 在这里我迷路了。我有一个匈牙利语键盘,只是找不到它的位置,只能找到]. 所以我可以在我无法输入的主题列表中滚动。
这只是开始:一般来说,我可以排除多少这种“奇怪”的组合?我该如何解决这些问题?匈牙利语的另一个问题是它有一堆额外的元音,它们必须映射到与英语相同数量的键上,它有 26 个字母(有 9 个额外的字母,所以 9 个键“丢失”了!)。这是否会妨碍使用 vim 编写匈牙利语文本(我需要这些键来生成特定于语言的元音)?
如何解决这个问题,如果我碰巧得到一个英文键盘(我猜 vim 最初是为哪种布局设计的),我就不必重新学习键的位置?(因为我周围没有英文键盘,所以我无法尝试会发生什么。因为我尝试了 'P' 左侧的两个键都不适合CTRL-]组合,而就我检查的布局而言,第二个应该是它。当然我的系统配置为匈牙利布局)
语言确实无关紧要,我想每个拥有非英语键盘的人都可能面临类似的问题。那么你如何解决这些问题呢?
有些架构具有多个地址空间,著名的例子是真正的哈佛架构,但 OpenCL 也具有此属性。
C 编译器可能为此提供一些解决方案,其中之一是命名地址空间,支持特殊的指针限定符来指示指针的地址空间,但也可能存在其他解决方案。
我不知道它们,并且在我使用的架构(8 位 AVR)上,还有另一种解决方案来处理该问题,即专门的宏 ( )pgmspace.h来处理 ROM 中的数据。但是对这些没有类型检查,并且它们(在我看来)使代码变得丑陋,所以在我看来,使用命名地址空间是一种更好的,甚至可能更便携的方式来处理问题(可移植的通过为地址空间限定符提供空定义,可以轻松地将这样的软件移植到具有单个地址空间的目标。
然而,在我从其可用性中了解到的上一个问题中,建议使用命名地址空间的解决方案被严重否决,此处:如何使两个其他相同的指针类型不兼容
反对者没有提供任何解释,我自己也没有找到任何解释,对我来说,命名地址空间似乎是处理问题的一种很好且功能完善的方式。
有人能提供解释吗?为什么不应该使用命名地址空间?(倾向于具有多个不同地址空间的目标上可用的任何其他方法)