C语言的隐藏特征
我知道所有C编译器实现背后都有一个标准,所以应该没有隐藏的特性。尽管如此,我相信所有的C开发人员都有他们一直在使用的隐藏/秘密技巧。使用INT(3)设置代码断点是我一直最喜欢的嗯。。。我认为C语言的一个优点是它的可移植性和标准性,因此每当我在当前使用的实现中发现一些“隐藏的技巧”时,我都会尽量不使用它,因为我会尽量保持我的C代码的标准性和可移植性。交错结构,如:C语言的隐藏特征,c,hidden-features,C,Hidden Features,我知道所有C编译器实现背后都有一个标准,所以应该没有隐藏的特性。尽管如此,我相信所有的C开发人员都有他们一直在使用的隐藏/秘密技巧。使用INT(3)设置代码断点是我一直最喜欢的嗯。。。我认为C语言的一个优点是它的可移植性和标准性,因此每当我在当前使用的实现中发现一些“隐藏的技巧”时,我都会尽量不使用它,因为我会尽量保持我的C代码的标准性和可移植性。交错结构,如: C有一个标准,但并非所有的C编译器都是完全兼容的(我还没有见过任何完全兼容的C99编译器!) 也就是说,我更喜欢那些不明显的、跨平台可
C有一个标准,但并非所有的C编译器都是完全兼容的(我还没有见过任何完全兼容的C99编译器!) 也就是说,我更喜欢那些不明显的、跨平台可移植的技巧,因为它们依赖于C语义。它们通常是关于宏或位运算的 例如:在不使用临时变量的情况下交换两个无符号整数:
...
a ^= b ; b ^= a; a ^=b;
...
或“扩展C”来表示有限状态机,如:
FSM {
STATE(x) {
...
NEXTSTATE(y);
}
STATE(y) {
...
if (x == 0)
NEXTSTATE(y);
else
NEXTSTATE(x);
}
}
这可以通过以下宏实现:
#define FSM
#define STATE(x) s_##x :
#define NEXTSTATE(x) goto s_##x
但是,总的来说,我不喜欢那些巧妙的技巧,但会使代码变得不必要的复杂易读(如swap示例),我喜欢那些使代码更清晰并直接传达意图的技巧(如FSM示例)。C编译器实现了几个标准之一。然而,有一个标准并不意味着语言的所有方面都有定义,例如,这是一个最受欢迎的“隐藏”功能,它变得如此流行,以至于现代编译器都有专用的识别代码,以确保优化技术不会破坏这种常用模式的预期效果 一般来说,不鼓励使用隐藏的特性或语言技巧,因为您在编译器使用的任何C标准的边缘上运行。许多这样的技巧在一个编译器到另一个编译器之间不起作用,并且通常这些类型的功能会从给定制造商的编译器套件的一个版本到另一个版本失败 破坏C代码的各种技巧包括:
当程序员对执行模型做出假设时会出现的其他问题,这些执行模型在大多数C标准中都被指定为“依赖于编译器”的行为。奇怪的向量索引:
int v[100]; int index = 10;
/* v[index] it's the same thing as index[v] */
匿名结构和数组是我最喜欢的。(参见) 或
它甚至可以用来实例化链表…函数指针。您可以使用函数指针表来实现,例如,快速间接线程代码解释器(FORTH)或字节码分派器,或者模拟类似OO的虚拟方法 然后在标准库中有隐藏的gem,例如qsort()、bsearch()、strpbrk()、strcspn()[后两个对于实现strtok()替换非常有用] C的一个错误特性是有符号算术溢出是未定义行为(UB)。所以,每当你看到一个表达式,如x+y,两者都是有符号的整数,它可能会溢出并导致UB。我从未使用过,但它们对于超低级别的东西听起来很酷
struct cat {
unsigned int legs:3; // 3 bits for legs (0-4 fit in 3 bits)
unsigned int lives:4; // 4 bits for lives (0-9 fit in 4 bits)
// ...
};
cat make_cat()
{
cat kitty;
kitty.legs = 4;
kitty.lives = 9;
return kitty;
}
这意味着sizeof(cat)
可以小到sizeof(char)
合并了和的评论,谢谢各位。多字符常量:
int x = 'ABCD';
这将x
设置为0x41424344
(或0x44434241
,具体取决于体系结构)
编辑:此技术不可移植,尤其是在序列化int时。
但是,创建自文档化枚举非常有用。e、 g
enum state {
stopped = 'STOP',
running = 'RUN!',
waiting = 'WAIT',
};
如果您正在查看原始内存转储,并且需要确定枚举的值,而不必查找它,这将使它变得更简单。这是GCC编译器的一个技巧,但是您可以向编译器提供分支指示提示(在Linux内核中很常见) 见: 我喜欢的是,它还为一些函数添加了一些表现力
void foo(int arg)
{
if (unlikely(arg == 0)) {
do_this();
return;
}
do_that();
...
}
我非常喜欢C99中添加的指定初始值设定项(gcc长期支持):
阵列初始化不再依赖于位置。如果更改FOO或BAR的值,数组初始化将自动对应于它们的新值。变量大小自动变量在某些情况下也很有用。这些都是在nC99中添加的,并且在gcc中得到了很长时间的支持
void foo(uint32_t extraPadding) {
uint8_t commBuffer[sizeof(myProtocol_t) + extraPadding];
最后,在堆栈上有一个缓冲区,可以容纳固定大小的协议头和可变大小的数据。使用alloca()可以获得相同的效果,但这种语法更紧凑
在调用此例程之前,必须确保extraPadding是一个合理的值,否则将导致堆栈崩溃。在调用malloc或任何其他内存分配技术之前,您必须检查参数是否正确,因此这并不罕见
int8_t
int16_t
int32_t
uint8_t
uint16_t
uint32_t
这些是标准中的可选项,但它必须是隐藏的特性,因为人们不断地重新定义它们。我研究过的一个代码库(目前仍然如此)有多个重新定义,都有不同的标识符。大多数情况下,它与预处理器宏有关:
#define INT16 short
#define INT32 long
等等。这让我想把头发拔出来只需使用标准的整数typedefs逗号运算符没有广泛使用。它当然可以被滥用,但也可能非常有用。这是最常见的用法:
for (int i=0; i<10; i++, doSomethingElse())
{
/* whatever */
}
将对每个语句求值,但表达式的值将是最后一个求值语句的值
#define FOO 16
#define BAR 3
myStructType_t myStuff[] = {
[FOO] = { foo1, foo2, foo3 },
[BAR] = { bar1, bar2, bar3 },
...
void foo(uint32_t extraPadding) {
uint8_t commBuffer[sizeof(myProtocol_t) + extraPadding];
int8_t
int16_t
int32_t
uint8_t
uint16_t
uint32_t
#define INT16 short
#define INT32 long
for (int i=0; i<10; i++, doSomethingElse())
{
/* whatever */
}
int j = (printf("Assigning variable j\n"), getValueFromSomewhere());
int my_printf (void *my_object, const char *my_format, ...)
__attribute__ ((format (printf, 2, 3)));
struct mystruct a = {0};
typedef struct {
int x;
int y;
} Point;
Point point_new(int x, int y)
{
Point p;
p.x = x;
p.y = y;
return p;
}
Point origin;
origin = point_new(0, 0);
//--- size of static_assertion array is negative if condition is not met
#define STATIC_ASSERT(condition) \
typedef struct { \
char static_assertion[condition ? 1 : -1]; \
} static_assertion_t
//--- ensure structure fits in
STATIC_ASSERT(sizeof(mystruct_t) <= 4096);
#define ERR(name, fmt, ...) fprintf(stderr, "ERROR " #name ": " fmt "\n", \
__VAR_ARGS__)
ERR(errCantOpen, "File %s cannot be opened", filename);
int x[] = { 1, 2, 3, };
enum foo { bar, baz, boom, };
struct foo{
int x;
int y;
char* name;
};
void main(){
struct foo f = { .y = 23, .name = "awesome", .x = -38 };
}
#include <stdio.h>
int main() {
int a = 3;
float b = 6.412355;
printf("%.*f\n",a,b);
return 0;
}
#define D 1
#define DD 2
enum CompileTimeCheck
{
MAKE_SURE_DD_IS_TWICE_D = 1/(2*(D) == (DD)),
MAKE_SURE_DD_IS_POW2 = 1/((((DD) - 1) & (DD)) == 0)
};
fd = open(PATH "/file", flags);
char buffer[256];
snprintf(buffer, 256, "%s/file", PATH);
fd = open(buffer, flags);
sscanf ( string, "%d%n", &number, &length );
string += length;
#include <stdio.h>
#include <stdlib.h>
int main()
{
1 || puts("Hello\n");
0 || puts("Hi\n");
1 && puts("ROFL\n");
0 && puts("LOL\n");
exit( 0 );
}
Hi
ROFL
struct {
int a:3;
int b:2;
int :0;
int c:4;
int d:3;
};
000aaabb 0ccccddd
0000aaab bccccddd