鉴于代码:
struct s1 {unsigned short x;};
struct s2 {unsigned short x;};
union s1s2 { struct s1 v1; struct s2 v2; };
static int read_s1x(struct s1 *p) { return p->x; }
static void write_s2x(struct s2 *p, int v) { p->x=v;}
int test(union s1s2 *p1, union s1s2 *p2, union s1s2 *p3)
{
if (read_s1x(&p1->v1))
{
unsigned short temp;
temp = p3->v1.x;
p3->v2.x = temp;
write_s2x(&p2->v2,1234);
temp = p3->v2.x;
p3->v1.x = temp;
}
return read_s1x(&p1->v1);
}
int test2(int x)
{
union s1s2 q[2]; …
Run Code Online (Sandbox Code Playgroud) 在标准化之前的C早期,实现有各种方法来处理各种操作的异常和半异常情况.如果没有先配置,其中一些会触发可能导致随机代码执行的陷阱.因为这些陷阱的行为超出了C标准的范围(并且在某些情况下可能由运行程序控制之外的操作系统控制),并且避免要求编译器不允许依赖此类陷阱的代码陷阱继续这样做,可能导致此类陷阱的操作行为完全取决于编译器/平台的判断.
到20世纪90年代末,虽然C标准没有要求这样做,但每个主流编译器都采用了许多这些情况的共同行为; 使用这样的行为将允许在代码速度,大小和可读性方面进行改进.
由于不再支持请求以下操作的"明显"方式,因此在使用较旧的编译器时,应如何以不妨碍可读性的方式替换它们,也不会对代码生成产生负面影响?出于描述的目的,假设int
是32位,ui
是unsigned int,si
是signed int,并且b
是unsigned char.
给定ui
和b
计算ui << b
b == 0..31,或者ui << (b & 31)
对于值32..255 可以任意表现为零的值.请注意,如果右侧操作数超过31时左侧操作数为零,则两种行为都是相同的.
对于只需要在右移或左移32到255时产生零的处理器上运行的代码,计算ui << b
b == 0..31和0 = b == 32..255.虽然编译器可能能够优化设计为跳过值32..255的移位的条件逻辑(因此代码只会执行将产生正确行为的移位),但我不知道如何制定这样的条件逻辑这将保证编译器不会为它生成不必要的代码.
与1和2一样,但是对于右移.
鉴于si
并且b
这样b0..30并且si*(1<<b)
不会溢出,计算si*(1<<b)
.请注意,使用乘法运算符会严重损害许多旧编译器的性能,但如果移位的目的是缩放有符号值,则在操作数在整个移位期间保持负值的情况下转换为无符号会感觉错误.
给定各种整数值,执行加法,减法,乘法和移位,如果没有溢出,结果将是正确的,并且如果存在溢出,代码将产生其高位表现为非陷阱和非陷阱的值. -UB但是以其他方式不确定时尚或将以可识别的平台定义的方式陷阱(并且在不支持陷阱的平台上,将简单地产生不确定的价值).
给定指向已分配区域的指针以及指向其中内容的指针,用于realloc
更改分配大小并调整上述指针以匹配,同时避免在realloc
返回原始块的情况下的额外工作.不一定可能在所有平台上,但是90年代主流平台都会允许代码确定是否realloc
导致事物移动,并通过减去该对象的前一个基地址来确定指针到死对象的偏移量(注意)调整需要通过计算与每个死指针相关联的偏移量,然后将其添加到新指针,而不是通过尝试计算旧指针和新指针之间的"差异" - 这将在许多情况下合法地失败分段架构).
"超现代"编译器是否为上述提供了任何良好的替代品,这些替代品不会降低代码大小,速度或可读性中的至少一个,而在任何其他代码中都没有改进?据我所知,整个20世纪90年代99%的编译器不仅可以完成上述所有工作,而且对于每个例子,人们都能够以相同的方式在几乎所有编译器上编写代码.一些编译器可能试图用无人看守的跳转表来优化左移和右移,但这是我能想到的唯一一种情况,即20世纪90年代的20世纪90年代平台编译器对"明显"的编码方式有任何问题任何上述内容.如果超现代编译器不再支持经典形式,它们作为替代品提供什么?
大多数微型计算机C编译器具有两个具有相同大小和表示的有符号整数类型,以及两个这样的无符号类型.如果int
是16位,则其表示通常匹配short
; 如果long
是64位,它通常会匹配long long
; 否则,int
而且long
通常具有匹配的32位表示.
如果在一个平台上long
,long long
并且int64_t
具有相同的表示,则需要按顺序将缓冲区传递给三个API函数(假设API在其他人的控制下并使用指示的类型;如果函数可以很容易地更改,它们可以简单地改为使用相同的类型.
void fill_array(long *dat, int size);
void munge_array(int64_t *dat, int size);
void output_array(long long *dat, int size);
Run Code Online (Sandbox Code Playgroud)
是否有任何有效的符合标准的方法允许所有三个函数使用相同的缓冲区而不要求在函数调用之间复制所有数据?我怀疑C的别名规则的作者是否认为这样的事情应该是困难的,但是对于"现代"编译器来说,假设没有任何写入的内容long*
将被读取long long*
,即使这些类型具有相同的表示也是时髦的.此外,虽然int64_t
通常将是相同的任一long
或long long
,实现是为不一致哪个.
在没有通过函数调用积极地进行基于类型的别名的编译器中,可以简单地将指针转换为正确的类型,可能包括静态断言以确保所有类型具有相同的大小.问题是,如果像扩展函数调用之类的gcc这样的编译器看到某些存储被写入long
并且稍后被读取long
,而没有任何类型的中间写入long
,则它可以用写入类型的值替换后面的读取long
,即使有介入类型的写入long long
.
完全禁用基于类型的别名当然是使这种代码工作的一种方法.任何体面的编译器都应该允许这样做,并且它将避免许多其他可能的陷阱.尽管如此,似乎应该有一种标准定义的方式来有效地执行这样的任务.在那儿?
有时,接受方法调用,使用参数完成并将其转换为MethodInvoker非常有用,该方法将使用这些参数调用指示的函数,而无需在此时指定参数.在其他时候,做类似的事情是有用的,但保留一些参数.这种类型的动作称为"Currying".在VB中执行此操作的最佳模式是什么?
在VB 2010中可以使用lambda表达式,但lambda表达式与edit-and-continue不兼容,并且它们创建的闭包可能具有意外的引用行为.另一种方法是定义一些通用方法,如下所示:
Public Module CurryMagic
Delegate Sub Action(Of T1, T2)(ByVal P1 As T1, ByVal P2 As T2)
Delegate Sub Action(Of T1, T2, T3)(ByVal P1 As T1, ByVal P2 As T2, ByVal P3 As T3)
Class CurriedAction0(Of FixedType1, FixedType2)
Dim _theAction As Action(Of FixedType1, FixedType2)
Dim _FixedVal1 As FixedType1, _FixedVal2 As FixedType2
Sub Exec()
_theAction(_FixedVal1, _FixedVal2)
End Sub
Sub New(ByVal theAction As Action(Of FixedType1, FixedType2), _
ByVal FixedVal1 As FixedType1, ByVal FixedVal2 As FixedType2)
_theAction = theAction
_FixedVal1 = FixedVal1 …
Run Code Online (Sandbox Code Playgroud) 在Cortex-M3指令集中,存在一系列LDREX/STREX指令,这样,如果使用LDREX指令读取某个位置,则只有在已知地址未被触及的情况下,以下STREX指令才能写入该地址.通常,效果是如果自LDREX以来没有发生中断(ARM术语中的"异常"),则STREX将成功,否则失败.
在Cortex M0中模拟这种行为最实用的方法是什么?我想为M3编写C代码并将其移植到M0.在M3上,人们可以说:
__inline void do_inc(unsigned int *dat) { while(__strex(__ldrex(dat)+1,dat)) {} }
执行原子增量.我能想到在Cortex-M0上实现类似功能的唯一方法是:
根据ldrex/strex函数的使用方式,禁用中断可能会合理地工作,但改变"load-exclusive"的语义似乎很糟糕,如果它被放弃会导致不良副作用.代码修补的想法似乎可以实现所需的语义,但它看起来很笨重.
(顺便说一句,附带问题:我想知道为什么M3上的STREX将成功/失败指示存储到寄存器而不是简单地设置一个标志?它的实际操作需要操作码中的四个额外位,要求有一个寄存器来保持成功/ failure指示,并要求使用"cmp r0,#0"来确定它是否成功.如果编译器没有将结果记录在寄存器中,那么编译器是否能够理智地处理STREX内在函数? ?进入寄存器需要两个简短的指示.)
我的妻子有一台Bernina刺绣机,我想尝试以编程方式创建设计.我想知道如何以机器软件接受的格式输出数据,或者找到一个免费或廉价的实用工具,可以从我可以生成的格式(例如HPGL或其他记录的内容)转换为格式机器可以接受.我的目的是输出一个每针一个XY坐标的文件,以及更改线程的指令(线程更改暂停); 我不需要生成区域填充,调整针距,优化拼接顺序等等; 我希望自己能处理这些事情.
有人有什么建议吗?
当给 ARM gcc 9.2.1 提供命令行选项-O3 -xc++ -mcpu=cortex-m0
[compile as C++] 和以下代码时:
unsigned short adjust(unsigned short *p)
{
unsigned short temp = *p;
temp -= temp>>15;
return temp;
}
Run Code Online (Sandbox Code Playgroud)
它产生合理的机器代码:
ldrh r0, [r0]
lsrs r3, r0, #15
subs r0, r0, r3
uxth r0, r0
bx lr
Run Code Online (Sandbox Code Playgroud)
这相当于:
unsigned short adjust(unsigned short *p)
{
unsigned r0,r3;
r0 = *p;
r3 = temp >> 15;
r0 -= r3;
r0 &= 0xFFFFu; // Returning an unsigned short requires...
return r0; // computing a …
Run Code Online (Sandbox Code Playgroud) 我有一些设计用于处理1-256字节的函数,在嵌入式C平台上运行,其中传递字节比传递int(一条指令对三条)更快更紧凑,编码它的首选方法是什么:
当系统忙时,预计函数的内环可能占处理器执行时间的15%-30%; 它有时会用于少量字节,有时用于大字节.该函数使用的内存芯片具有每事务开销,我更喜欢让我的内存访问函数在内部执行start-transaction/do-stuff/end-transaction序列.
最有效的代码是简单地接受unsigned char并将参数值0视为执行256字节的请求,依赖于调用者以避免任何意外尝试读取0字节.但这似乎有点危险.有其他人在嵌入式系统上处理过这些问题吗?他们是如何处理的?
编辑 该平台是PIC18Fxx(128K代码空间; 3.5K RAM),连接到SPI闪存芯片; 当预期较少时读取256个字节可能会超出PIC中的读缓冲区.写入256个字节而不是0将破坏闪存芯片中的数据.如果没有检查忙状态,PIC的SPI端口每12个指令时间限制为一个字节; 如果有人会慢一些.典型的写事务除了要接收的数据外还需要发送4个字节; 读取需要额外的字节用于"SPI周转"(访问SPI端口的最快方法是在发送下一个字节之前读取最后一个字节).
编译器是HiTech PICC-18std.
我一般都喜欢HiTech的PICC-16编译器; HiTech似乎已经将他们的能量从PICC-18std产品转移到他们的PICC-18pro系列,它的编译时间更慢,似乎需要使用3字节"const"指针而不是双字节指针,并且有它的关于内存分配的想法.也许我应该更多地看看PICC-18pro,但是当我尝试在一个版本的PICC-18pro上编译我的项目时,它没有用,我也没弄清楚为什么 - 也许是关于变量布局不同意的事情我的asm例程 - 我只是继续使用PICC-18std.
顺便说一句,我刚刚发现PICC-18特别喜欢do {} while( - bytevar); 特别不喜欢做{} while( - intvar); 我想知道编译器生成后者时会发生什么?
do { local_test++; --lpw; } while(lpw); 2533 ;newflashpic.c: 792: do 2534 ;newflashpic.c: 793: { 2535 0144A8 2AD9 incf fsr2l,f,c 2536 ;newflashpic.c: 795: } while(--lpw); 2537 0144AA 0E00 movlw low ?_var_test 2538 0144AC 6EE9 movwf fsr0l,c 2539 0144AE …
即使C标准明确认识到,指向一个对象“刚刚过去”的地址可能偶然地与指向“指向”另一个不相关对象的地址相等,但gcc和clang似乎都在假设没有指针的情况下运行如示例所示,观察到指向刚刚超过一个对象的对象可能指向另一个对象:
#include <stdio.h>
int x[1],y[1];
int test1(int *p)
{
y[0] = 1;
if (p==x+1)
*p = 2; // Note that assignment is to *p and not to x[1] !!!
return y[0];
}
int test2(int *p)
{
x[0] = 1;
if (p==y+1)
*p = 2; // Note that assignment is to *p and not to y[1] !!!
return x[0];
}
int (*volatile test1a)(int *p) = test1;
int (*volatile test2a)(int *p) = test2;
int main(void) {
int q;
printf("%llX\n",(unsigned long long)y - …
Run Code Online (Sandbox Code Playgroud) 在涉及不可变对象的某些情况下,许多不同的对象可能存在,这些对象在语义上是相同的.一个简单的例子是从文件中读取许多行文本到字符串.从程序的角度来看,两行具有相同字符序列的事实将是"巧合",但从程序员的角度来看,可能会出现大量重复.如果许多字符串实例相同,将对这些不同实例的引用更改为对单个实例的引用将节省内存,并且还将促进它们之间的比较(如果两个字符串引用指向相同的字符串,则不需要执行字符 - 通过字符比较来确定它们是相同的).
对于某些情况,系统提供的字符串实习设施可能很有用.但是,它有一些严重的限制:
如果存在true WeakDictionary<ImmutableClassType, ImmutableClassType>
(对于每个元素,键和值将相同),代码可以执行以下操作:
if (theDict.TryGetValue(myString, ref internedString))
myString = internedString;
else
theDict[myString] = myString;
Run Code Online (Sandbox Code Playgroud)
不幸的是,我不知道WeakDictionary<keyType, valType>
.net中的任何内置类.此外,当两个引用总是指向同一个东西时,为每个项的键和值生成弱引用似乎是浪费的.
我已经阅读了一些关于ConditionalWeakTable
,这听起来像一个有趣的类,但我不认为它在这里可用,因为目标是能够采用一个实例并找到另一个在语义上等效的独立实例.
对于类的实例具有明确定义的生命周期的情况,使用常规Dictionary
来合并相同的实例可能是合理的.然而,在许多情况下,可能很难知道何时Dictionary
应放弃这样的物品或清除其中的物品.WeakReference
基于A 的实习收集将避免此类问题.这样的事情是存在的,还是可以很容易实现的?
附录
正如svick所指出的那样,Dictionary<WeakReference, WeakReference>
问题会有些问题,因为没有切实可行的方法来定义IEqualityComparer
哪一个会实时WeakReference
返回GetHashCode
其目标的值,并且有一个死人继续返回该值.可以定义一个结构,它将包含一个整数target-hashcode值(在构造函数中设置),并且它自己GetHashCode
将返回该整数.稍微改进可能是使用a ConditionalWeakTable
将目标链接WeakReference
到可终结对象,该目标可用于将表项排入队列以进行删除.
我不确定尝试急切地清理字典与采取更为被动的方法之间的适当平衡是什么(例如,如果自上次扫描以来至少有一个GC,则在添加项目时执行扫描,以及数字自上次扫描以来添加的项目超过了幸存的项目数量).扫描字典中的所有内容并不是免费的,但ConditionalWeakTable可能也不会是免费的.
PPS
我想到的另一个概念,但我认为它可能不如弱实用方法那样有用,就是让一个逻辑不可变类型保存一个可变的"timestamp"值,并且有一个接受的比较方法它的论点ref
.如果发现两个不同的实例相等,则将检查它们的时间戳值.如果两者都为零,则将从全局计数器(-1,-2,-3等)中为它们分配连续的负数.具有(或被分配)较低时间戳值的参数然后将被另一个替换.
使用这种方法,如果许多对象实例被重复地相互比较,则许多引用将被替换为对"较旧"实例的引用.根据使用模式,这可能导致大多数相同的对象实例被合并而不使用任何类型的实习字典.但是,对嵌套对象应用这种方法需要"不可变"对象允许嵌套对象引用变异以指向其他所谓相同的嵌套对象.如果"假设相同"的对象总是如此,那么这应该没问题,但如果不是这样,可能会导致相当奇怪的错误行为.