在C中,typedef
使用这种结构可以使用数组:
typedef int table_t[N];
这里,table_t
现在定义为N的数组int
.声明的任何变量table_t t;
现在将表现为正常数组int
.
这种构造的要点是在函数中用作参数类型,例如:
int doSomething(table_t t);
一个相对等效的函数原型可能是:
int doSomething(int* t);
第一个结构的优点是它强制执行N
表的大小.在许多情况下,强制执行此属性更安全,而不是依赖程序员来正确地找出这种情况.
现在一切都很好,除了为了保证表的内容不会被修改,有必要使用const
限定符.
以下陈述相对简单易懂:
int doSomething(const int* t);
现在,doSomething
保证它不会修改作为指针传递的表的内容.现在,这几乎相同的建筑怎么样?:
int doSomething(const table_t t);
这是什么const
?表的内容,或指向表的指针?如果它是指针const
,是否有另一种方式(C90兼容)保留定义表大小的能力并告诉它的内容将是const?
请注意,有时还需要修改表的内容,因此该const
属性不能嵌入到typedef定义中.
[ 编辑 ]感谢到目前为止收到的优秀答案.总结一下:
const
属性的行为也与指针相同(与指针类型的typedef形成鲜明对比,如下面的@random所示)我正在尝试创建一个C源代码,无论目标系统的字节顺序如何,它都能正确处理I/O.
我选择了"little endian"作为我的I/O约定,这意味着,对于大端CPU,我需要在写入或读取时转换数据.
转换不是问题.我面临的问题是检测字节序,最好是在编译时(因为CPU在执行过程中不会改变字节序...).
到目前为止,我一直在使用这个:
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
...
#else
...
#endif
Run Code Online (Sandbox Code Playgroud)
它被记录为GCC预定义的宏,而Visual似乎也理解它.
但是,我收到报告说某些big_endian系统(PowerPC)的检查失败了.
所以,我正在寻找一个万无一失的解决方案,确保无论编译器和目标系统如何都能正确检测到字节顺序.好吧,他们中的大多数至少......
[编辑]:提出的大多数解决方案都依赖于"运行时测试".编译期间编译器有时可以正确评估这些测试,因此不会产生实际的运行时性能.
然而,用某种<< if (0) { ... } else { ... }
>> 分支是不够的.在当前的代码实现中,变量和函数声明依赖于big_endian检测.使用if语句无法更改这些内容.
嗯,显然,有后备计划,即重写代码......
我宁愿避免这种情况,但是,它看起来像是一个越来越少的希望......
[编辑2]:我通过深度修改代码测试了"运行时测试".尽管他们正确地完成了工作,但这些测试也会影响性能.
我期待着,因为测试具有可预测的输出,编译器可以消除坏分支.但不幸的是,它并不是一直有效.MSVC是一个很好的编译器,并且成功地消除了坏分支,但是GCC的结果是混合的,这取决于版本,测试类型,以及对64位比对32位的影响更大.
真奇怪.而且这也意味着无法确保编译器处理运行时测试.
编辑3:这些天,我正在使用编译时常量联合,期望编译器将其解析为明确的是/否信号.它运作得很好:https: //godbolt.org/g/DAafKo
过去,ARM处理器无法正确处理未对齐的内存访问(ARMv5及更低版本).喜欢的东西u32 var32 = *(u32*)ptr;
也只是失败(引发异常),如果ptr
没有正确的4字节对齐.
编写这样的语句对于x86/x64可以正常工作,因为这些CPU总是非常有效地处理这种情况.但根据C标准,这不是一种"正确"的写作方式.u32
显然等同于4个字节的结构,必须在4个字节上对齐.
在保持正统正确性并确保与任何cpu完全兼容的同时获得相同结果的正确方法是:
u32 read32(const void* ptr)
{
u32 result;
memcpy(&result, ptr, 4);
return result;
}
Run Code Online (Sandbox Code Playgroud)
这个是正确的,将为任何能够或不在未对齐位置读取的CPU生成适当的代码.更好的是,在x86/x64上,它已针对单个读取操作进行了适当优化,因此具有与第一个语句相同的性能.它便携,安全,快速.谁能问更多?
好吧,问题是,在ARM上,我们没有那么幸运.
编写memcpy
版本确实是安全的,但似乎导致系统谨慎的操作,这对于ARMv6和ARMv7(基本上是任何智能手机)来说都非常慢.
在一个严重依赖读取操作的性能导向应用程序中,可以测量第一版和第二版之间的差异:它在设置时 大于5倍gcc -O2
.这太过不容忽视了.
试图找到一种使用ARMv6/v7功能的方法,我寻找了几个示例代码的指导.不幸的是,他们似乎选择了第一个声明(直接u32
访问),这不应该是正确的.
这还不是全部:新的GCC版本现在正在尝试实现自动矢量化.在x64上,这意味着SSE/AVX,在ARMv7上意味着NEON.ARMv7还支持一些新的"加载多个"(LDM)和"存储多个"(STM)操作码,这些操作码需要指针对齐.
那是什么意思 ?好吧,编译器可以自由地使用这些高级指令,即使它们没有从C代码中特别调用(没有内在的).为了做出这样的决定,它使用a u32* pointer
应该在4个字节上对齐的事实.如果不是,那么所有的赌注都是关闭的:未定义的行为,崩溃.
这意味着即使在支持未对齐内存访问的CPU上,使用直接u32
访问也是危险的,因为它可能导致在高优化设置下生成错误的代码(-O3
).
那么现在,这是一个困境:如何在未对齐内存访问的情况下访问ARMv6/v7的本机性能而无需编写错误的版本u32
访问权限?
PS:我也试过__packed()
说明,从性能的角度看,它们似乎与memcpy
方法完全一样.
[编辑]:感谢迄今为止收到的优秀元素.
看看生成的程序集,我可以确认@Notlikethat发现该memcpy
版本确实生成了正确的ldr
操作码(未对齐的加载).但是,我还发现生成的程序集无用地调用str
(命令).因此,完整的操作现在是一个未对齐的加载,一个对齐的存储,然后是一个最终对齐的加载.这比必要的工作要多得多.
回答@haneefmubarak,是的,代码正确内联.而且,不是,memcpy
提供最佳速度是非常远的,因为强制代码接受直接u32 …
我目前正在开发一种非常快速的算法,其中一部分是极快的扫描和统计功能.在这个任务中,我追求任何性能优势.因此,我也有兴趣保持代码"多线程"友好.
现在的问题是:我注意到将一些非常频繁访问的变量和数组放入"全局"或"静态本地"(它们都是相同的),有一个可衡量的性能优势(在+ 10%的范围内) .我试图理解为什么,并找到一个解决方案,因为我宁愿避免使用这些类型的分配.请注意,我不认为差异来自"分配",因为在堆栈上分配一些变量和小数组几乎是瞬间完成的.我相信差异来自"访问"和"修改"数据.
在这次搜索中,我发现了stackoverflow的这篇旧帖子: 全局变量的C++性能
但我对那里的答案感到非常失望.很少解释,大多是咆哮"你不应该这样做"(嘿,这不是问题!)和非常粗略的陈述,如'它不影响性能',这显然是不正确的,因为我用精确测量它基准工具.
如上所述,我正在寻找解释,并且,如果存在,则解决此问题.到目前为止,我已经感觉到计算本地(动态)变量的内存地址比全局(或本地静态)花费更多.也许类似于ADD操作差异.但这无助于找到解决方案......
通常最好将CPU寄存器用于其全部容量.对于便携式代码,它意味着在64位CPU上使用64位算术和存储,在32位CPU上仅使用32位(否则,将在32位模式下模拟64位指令,从而导致毁灭性的表演).
这意味着有必要检测CPU寄存器的大小,通常是在编译时(因为运行时测试很昂贵).
多年来,我一直使用简单的启发式方法sizeof(nativeRegisters) == sizeof(size_t)
.
它在许多平台上运行良好,但它似乎是linux x32的错误启发式:在这种情况下,size_t
只有32位,而寄存器仍然可以处理64位.它会导致一些性能损失(对我的用例来说很重要).
即使在这种情况下,我也想正确检测CPU寄存器的可用大小.
我怀疑我可以尝试在特殊情况x32模式下找到一些特定于编译器的宏.但我想知道是否存在更通用的东西,以涵盖更多情况.例如,另一个目标是OpenVMS 64位:那里,本机寄存器大小是64位,但size_t
只是32位.
在设计C接口时,通常只允许.h
用户程序需要知道的公共接口().
因此,例如,如果用户程序不需要知道它们,则结构的内部组件应该保持隐藏.这确实是一种很好的做法,因为结构的内容和行为将来可能会发生变化,而不会影响界面.
实现该目标的一个好方法是使用不完整的类型.
typedef struct foo opaqueType;
现在opaqueType
可以构建仅使用指针的接口,而无需用户程序知道内部工作struct foo
.
但有时,可能需要静态地(通常在堆栈上)分配此类结构,以解决性能和内存碎片问题.显然,上面的结构,opaqueType
是不完整的,所以它的大小是未知的,所以它不能静态分配.
解决方法是分配"shell类型",例如:
typedef struct { int faketable[8]; } opaqueType;
上面的构造强制执行大小和对齐,但不会进一步描述结构真正包含的内容.因此它符合保持"不透明"类型的目标.
它主要起作用.但在一种情况下(GCC 4.4),编译器抱怨它打破了严格别名,并且它生成了错误的二进制文件.
现在,我已经阅读了大量关于严格混叠的内容,所以我想我现在明白这意味着什么.
问题是:有没有办法定义一个opaque类型,它仍然可以在堆栈上分配,而不会破坏严格的别名规则?
请注意,我已经尝试了这篇优秀文章中描述的union方法,但它仍然会生成相同的警告.
另请注意,visual,clang和gcc 4.6及更高版本不会抱怨并且可以正常使用此构造.
[编辑]信息补充:
根据测试,问题只发生在以下情况:
.c
文件中的私有类型.如果他们是同一个联盟的一部分,那显然无关紧要.公共类型是否包含无关紧要char
.最后,我的目标是C90.也许C99如果真的没有选择.
我目前正在经历一些奇怪的效果gcc
(测试版本:4.8.4).
我有一个性能导向的代码,运行速度非常快.它的速度在很大程度上取决于内联许多小功能.
由于内联多个.c
文件很困难(-flto
尚未广泛使用),因此我将许多小函数(通常每行1到5行)保存到一个公共C文件中,我正在开发一个编解码器,其相关解码器.根据我的标准,它"相对"大(约2000行,虽然其中很多只是注释和空白行),但将它分成更小的部分会带来新的问题,所以如果可能的话,我宁愿避免这样做.
编码器和解码器是相关的,因为它们是逆运算.但是从编程的角度来看,它们是完全分离的,除了少量的typedef和非常低级的函数(例如从未对齐的内存位置读取)之外,没有任何共同点.
奇怪的效果是这个:
我最近fnew
在编码器方面添加了一个新功能.这是一个新的"切入点".它不会在.c
文件中的任何位置使用或调用.
它存在的简单事实使得解码器功能的性能fdec
大幅下降超过20%,这是太多不容忽视的.
现在,记住比编码和解码操作是完全分开的,并分享几乎没有,节省一些小的typedef
(u32
,u16
关系等)以及相关的操作(读/写).
当定义新的编码功能fnew
如static
,解码器的性能fdec
提高了恢复正常.既然fnew
没有从中调用.c
,我想它就像它不存在一样(死代码消除).
如果static fnew
现在从编码器端调用,则性能fdec
仍然很强.
但是一旦fnew
修改,fdec
性能就会大幅下降.
假设fnew
修改超过了一个阈值,我增加了以下gcc
参数:( --param max-inline-insns-auto=60
默认情况下,它的值应该是40.)并且它起作用:性能fdec
现在恢复正常.
而且我猜这个游戏将永远持续每一个小修改fnew
或其他类似的东西,需要进一步调整.
这简直太奇怪了.fnew
对于完全不相关的函数具有连锁效应,函数中的一些小修改是没有逻辑上的原因fdec
,只有关系是在同一个文件中.
到目前为止,我可以发明的唯一一个初步的解释是,简单的存在可能fnew
足以跨越某种global file threshold
会影响的东西fdec
.fnew
可以在以下情况下"不存在":1.不存在,2.static …
在有人抱怨"重复"之前,我一直在彻底检查SO,但似乎还没有干净的答案,尽管问题看起来很简单.
我正在寻找一个可移植的C代码,它能够提供文件的大小,即使这样的文件大于4GB.
通常的方法(fseek,ftell)工作正常,只要文件仍然<2GB.它在各处得到了相当好的支持,所以我试图找到相同的东西.
不幸的是,所有编译器都不支持更新的方法(fseeko,ftello).例如,MinGW错过了(显然是MSVC).此外,一些评论让我相信新的返回类型(off_t)不一定支持> 2GB的大小,它可能依赖于一些外部参数来进行检查.
MSVC不支持明确的方法(fseeko64,ftello64).MS提供等效的_fseeki64和_ftelli64.这已经很糟糕,但它变得更糟:一些Linux配置似乎在运行时严重支持这些功能.例如,我在PowerPC上的Debian Squeeze,使用GCC 4.4,将使用fseeko64生成一个"filesize"方法,它始终返回0(虽然它适用于Ubuntu64).MinGW似乎回答了2GB以上的随机垃圾.
嗯,就可移植性而言,我有点无能为力.如果我需要制作一堆#if #else,那么为什么不首先直接使用OS和编译器细节方法,例如用于MSVC的GetFileSize().
我有兴趣研究最近接受的C++ 20合同编程,用于学习和调查目的.
当我正在寻找编译器支持时,我很失望,没有找到任何.双方gcc
并clang
都相当明确表示,他们不其内支持此功能--std=c++2a
模式.
由于批准是最近的,我不会惊讶于当前的编译器不支持所提出的确切语义.更令我惊讶的是,绝对没有任何东西,即使是特定于编译器的扩展,即使在有限的方式下也会模仿相同的功能.
我期待C++委员会只考虑在现场证明自己的功能,通常是通过编译器或目标特定扩展.批准C++ 20的此功能表明该功能应该可以在某处访问.但到目前为止,我一直无法找到它.
目前有没有办法试验C++合同编程?甚至使用一组特定于编译器的扩展?
我目前正在尝试构建一个配置来测试Big-Endian系统上的一些代码.
通过聊天和研究,我一直相信这些测试的一个好目标是PowerPC架构.由于我没有自己的,并且不希望很快就能直接访问,我正在寻找某种仿真软件来测试我的代码.
问题是,我发现在这方面没有"易于使用"的解决方案.
似乎至少有两种可能的解决方案,一种使用QEMU,另一种使用PearPC.它们都不容易部署.
我看到它的方式:
我想要像部署VMWare虚拟机一样简单,只需一个简单的ghost VM即可使用和下载.
附加信息:我认为PowerPC上的Linux可能是更好的操作系统选择,因为模拟MAC环境可能会破坏许可证.我猜QEMU也比PearPC更新,更受支持.主机系统可以是Windows或Linux.主机CPU必须是x86.
c ×8
gcc ×2
performance ×2
arm ×1
arrays ×1
benchmarking ×1
c++ ×1
c++20 ×1
clang ×1
compile-time ×1
const ×1
contract ×1
emulation ×1
endianness ×1
file ×1
filesize ×1
global ×1
inlining ×1
large-files ×1
powerpc ×1
qemu ×1
stack ×1
static ×1
typedef ×1