zwo*_*wol 12 c standards portability
这是一个挑剔的细节问题,有三个部分.上下文是我希望说服一些人无条件地使用无条件<stddef.h>的定义offsetof而不是(在某些情况下)滚动他们自己的定义是安全的.有问题的程序完全用普通的旧C编写,所以在回答时请完全忽略C++.
第1部分:当以与标准相同的方式使用时offsetof,此宏的扩展是否会引发每个C89的未定义行为,为什么或为什么不行,并且它在C99中是不同的?
#define offset_of(tp, member) (((char*) &((tp*)0)->member) - (char*)0)
Run Code Online (Sandbox Code Playgroud)
注意:人们感兴趣的所有实现都取代了标准的规则,指针只有在指向同一个数组时才能相互减去,通过定义所有指针,无论类型或值,指向单个指针全球地址空间.因此,在争论这个宏的扩张引发未定义的行为时,请不要依赖于该规则.
第2部分:据您所知,有没有一个已发布的生产C实现,当进行上述宏的扩展时,(在某些情况下)会表现出与offsetof使用其宏时的情况不同的行为?
第3部分:据您所知,最近发布的生产C实现是什么,或者未提供stddef.h或未提供offsetof该标题中的工作定义?该实现是否声称符合任何版本的C标准?
对于第2部分和第3部分,请仅在您可以命名特定实现并给出发布日期时回答.说明可能符合条件的实现的一般特征的答案对我没用.
R..*_*R.. 10
无法编写便携式offsetof宏.你必须使用提供的那个stddef.h.
关于您的具体问题:
stddef.h和offsetof.预ANSI编译器可能缺乏它,但它们有更多的基本问题,使它们无法用于现代代码(例如缺少void *和const).而且,即使一些理论编译器确实缺乏stddef.h,你也可以提供替代,就像人们投入stdint.h使用MSVC一样...
回答#2:是的,gcc-4*(我目前正在研究2009年8月4日发布的v4.3.4,但它应该适用于迄今为止所有的gcc-4版本).在stddef.h中使用以下定义:
#define offsetof(TYPE, MEMBER) __builtin_offsetof (TYPE, MEMBER)
Run Code Online (Sandbox Code Playgroud)
__builtin_offsetof内置的编译器在哪里sizeof(也就是说,它不是作为宏或运行时函数实现的).编译代码:
#include <stddef.h>
struct testcase {
char array[256];
};
int main (void) {
char buffer[offsetof(struct testcase, array[0])];
return 0;
}
Run Code Online (Sandbox Code Playgroud)
使用你提供的宏的扩展会导致错误("数组'缓冲区的大小'不是一个整数的常量表达式")但是在使用stddef.h中提供的宏时会起作用.使用gcc-3构建使用类似于你的宏.我想gcc开发人员对这里已经表达的未定义行为等有很多相同的关注,并且创建了内置的编译器,作为尝试在C代码中生成等效操作的更安全的替代方法.
附加信息:
offsetof关于你的其他问题:我认为就问题#1而言,R的回答和随后的评论很好地概述了标准的相关部分.至于你的第三个问题,我还没有听说过没有的现代 C编译器stddef.h.我当然不会认为任何编译器缺少像"生产"这样的基本标准头.同样,如果它们的offsetof实现不起作用,那么编译器在被认为是"生产"之前仍然有工作要做,就像stddef.h(如NULL)中的其他东西不起作用一样.在C标准化之前发布的AC编译器可能没有这些东西,但ANSI C标准已有20多年的历史,因此您不太可能遇到其中之一.
这个问题的整个前提提出了一个问题:如果这些人确信他们不能信任offsetof编译器提供的版本,那么他们可以信任什么呢?他们相信这NULL是正确定义的吗?他们相信这long int不比常规int吗?他们相信这样的memcpy作品应该如此吗?他们是否推出了自己的C标准库功能的其余版本?拥有语言标准的一个重要原因是,您可以信任编译器正确地执行这些操作.信任编译器除了之外的其他一切似乎很愚蠢offsetof.
更新:(回应您的意见)
我想,我的同事的行为像您这样做:-)我们的一些旧代码仍然具有自定义宏定义NULL,VOID以及其他类似的东西,因为"不同的编译器可以不同的方式实现他们"(叹气).其中一些代码是在C标准化之前写回来的,许多老开发人员仍然处于这种心态,即使C标准明确说明不是这样.
你可以做的一件事就是证明他们错了,同时让每个人都开心:
#include <stddef.h>
#ifndef offsetof
#define offsetof(tp, member) (((char*) &((tp*)0)->member) - (char*)0)
#endif
Run Code Online (Sandbox Code Playgroud)
实际上,他们将使用提供的版本stddef.h.但是,如果您遇到未定义它的假设编译器,则自定义版本将始终存在.
基于我多年来的类似对话,我认为offsetof不属于标准C 的信念来自两个地方.首先,它是一个很少使用的功能.开发人员不经常看到它,所以他们忘记它甚至存在.其次,offsetof在Kernighan和Ritchie的开创性着作"The C Programming Language"(即使是最新版本)中也没有提及.本书的第一版是C标准化之前的非官方标准,我经常听到人们错误地将该书称为该语言的标准.阅读比官方标准容易得多,所以我不知道是否因为把它作为第一个参考点而责怪他们.然而,无论他们认为什么,标准都是offsetofANSI C的一部分(参见R的链接答案).
这是另一种看问题#1的方法.在ANSI C标准给出了4.1.5节定义如下:
Run Code Online (Sandbox Code Playgroud)offsetof( type, member-designator)它扩展为一个整数常量表达式,其类型为size_t,其值是以字节为单位的偏移量,从结构的开头(由类型指定)到结构成员(由member-designator指定).
使用offsetof宏不会调用未定义的行为.实际上,行为就是标准实际定义的所有行为.由编译器编写者来定义offsetof宏,使其行为遵循标准.无论是使用宏,内置编译器还是其他东西来实现,确保它按预期运行,都要求实现者深入理解编译器的内部工作方式以及它将如何解释代码.编译器可以使用宏来实现它,就像你提供的惯用版本一样,但这只是因为他们知道编译器将如何处理非标准代码.
另一方面,您提供的宏扩展确实会调用未定义的行为.由于您对编译器的了解不足以预测它将如何处理代码,因此无法保证特定的实现offsetof始终有效.许多人定义了他们自己的版本,并没有遇到问题,但这并不意味着代码是正确的.即使这是特定编译器碰巧定义的方式offsetof,自己编写代码也会调用UB,而使用提供的offsetof宏则不会.
offsetof如果不调用未定义的行为,则无法完成滚动自己的宏(ANSI C部分A.6.2"未定义的行为",第27个项目符号点).使用stddef.h的版本offsetof将始终产生标准中定义的行为(假设符合标准的编译器).我建议不要定义自定义版本,因为它可能导致可移植性问题,但如果其他人无法说服,那么#ifndef offsetof上面提供的代码段可能是一个可接受的折衷方案.
| 归档时间: |
|
| 查看次数: |
3027 次 |
| 最近记录: |