C 语言的隐性特征

我知道所有 C 编译器实现背后都有一个标准,所以应该没有隐藏的特性。尽管如此,我相信所有 C 开发人员都有他们一直在使用的隐藏/秘密技巧。

86538 次浏览

使用 INT (3)在代码中设置断点是我一直以来的最爱

我认为 C 语言的优点之一就是它的可移植性和标准性,所以当我发现我正在使用的实现中有一些“隐藏的技巧”时,我尽量不去使用它,因为我尽可能保持我的 C 代码的标准和可移植性。

达夫的装置这样的交错结构:

strncpy(to, from, count)
char *to, *from;
int count;
{
int n = (count + 7) / 8;
switch (count % 8) {
case 0: do { *to = *from++;
case 7:      *to = *from++;
case 6:      *to = *from++;
case 5:      *to = *from++;
case 4:      *to = *from++;
case 3:      *to = *from++;
case 2:      *to = *from++;
case 1:      *to = *from++;
} while (--n > 0);
}
}

Gcc 的早期版本尝试在源代码中遇到“ # 杂注”时运行游戏。

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

总的来说,我不喜欢那些聪明的技巧,但是它们会让代码变得不必要的复杂(比如交换的例子) ,而且我喜欢那些让代码更清晰、直接表达意图的技巧(比如 FSM 的例子)。

C 编译器实现了几种标准之一。然而,拥有一个标准并不意味着定义了语言的所有方面。例如,达夫的装置是一个非常受欢迎的“隐藏”特性,它已经变得非常流行,以至于现代编译器都有特殊用途的识别代码来确保优化技术不会破坏这种常用模式的预期效果。

一般来说,当你运行在编译器使用的 C 标准的边缘时,隐藏的特性或者语言技巧是不被鼓励的。许多这样的技巧在不同的编译器之间不起作用,而且通常这些特性会在不同的编译器版本之间失效。

破解 C 代码的各种技巧包括:

  1. 依赖于编译器如何在内存中布局结构。
  2. 整数/浮点数的 Endianness假设。
  3. 函数 ABI 的假设。
  4. 对堆栈帧增长方向的假设。
  5. 关于语句中执行顺序的假设。
  6. 关于函数参数中语句执行顺序的假设。
  7. 假设位的大小或精度为短型、整型、长型、浮型和双型。

当程序员对大多数 C 标准中指定为“编译器依赖”行为的执行模型做出假设时,就会出现其他问题。

奇怪的矢量索引:

int v[100]; int index = 10;
/* v[index] it's the same thing as index[v] */

匿名结构和数组是我最喜欢的一个

setsockopt(yourSocket, SOL_SOCKET, SO_REUSEADDR, (int[]){1}, sizeof(int));

或者

void myFunction(type* values) {
while(*values) x=*values++;
}
myFunction((type[]){val1,val2,val3,val4,0});

它甚至可以用来实例化链表... ..。

函数指针。您可以使用函数指针表来实现,例如,快速间接线程代码解释器(FORTH)或字节码调度程序,或者模拟类似于 OO 的虚拟方法。

然后在标准库中存在隐藏的 gem,比如 qsort ()、 bsearch ()、 strpbrk ()、 strcspn ()[后两个对于实现 strtok ()替换非常有用]。

C 语言的一个不足之处在于,签名的算术溢出是未定义行为的。因此,无论何时您看到像 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)一样小。


亚伦Leppie合并评论,谢谢大家。

多字符常量:

int x = 'ABCD';

这将 x设置为 0x41424344(或 0x44434241,取决于架构)。

编辑: 此技术不可移植,特别是如果序列化 int。 但是,创建自记录枚举可能非常有用。

enum state {
stopped = 'STOP',
running = 'RUN!',
waiting = 'WAIT',
};

如果您正在查看一个原始内存转储,并且需要确定枚举的值而不必查找它,那么这样做会简单得多。

我曾经看过一段代码,然后问它是做什么的:


hexDigit = "0123456789abcdef"[someNybble];

另一个最喜欢的是:


unsigned char bar[100];
unsigned char *foo = bar;
unsigned char blah = 42[foo];

不是什么隐藏的特征,但在我看来像巫术,我第一次看到这样的东西:


void callback(const char *msg, void *data)
{
// do something with msg, e.g.
printf("%s\n", msg);


return;
data = NULL;
}

这种构造的原因是,如果使用-Wtra 编译这个命令,而不使用“ data = NULL;”-line,gcc 将显示一个关于未使用参数的警告。但是用这个没用的台词,你不会得到警告。

编辑: 我知道还有其他(更好的)方法来防止这些警告。我第一次看到这个的时候,感觉很奇怪。

这更像是 GCC 编译器的一个技巧,但是您可以向编译器提供分支指示提示(在 Linux 内核中很常见)

#define likely(x)       __builtin_expect((x),1)
#define unlikely(x)     __builtin_expect((x),0)

见: http://kerneltrap.org/node/4705

我喜欢它的地方在于,它还为一些函数增加了一些表达能力。

void foo(int arg)
{
if (unlikely(arg == 0)) {
do_this();
return;
}
do_that();
...
}

我非常喜欢 C99中添加的指定初始化程序(长期以来在 gcc 中得到支持) :

#define FOO 16
#define BAR 3


myStructType_t myStuff[] = {
[FOO] = { foo1, foo2, foo3 },
[BAR] = { bar1, bar2, bar3 },
...

数组初始化不再依赖于位置。如果更改 FOO 或 BAR 的值,数组初始化将自动对应于它们的新值。

可变大小的自动变量在某些情况下也很有用。这些都是在 nC99中添加的,并且在 gcc 中得到了很长时间的支持。

void foo(uint32_t extraPadding) {
uint8_t commBuffer[sizeof(myProtocol_t) + extraPadding];

您最终将在堆栈上获得一个缓冲区,该缓冲区为固定大小的协议头加上可变大小的数据提供了空间。您可以使用 alloca ()获得相同的效果,但是这种语法更加紧凑。

在调用这个例程之前,您必须确保外部填充是一个合理的值,否则会导致堆栈崩溃。在调用 malloc 或任何其他内存分配技术之前,您必须理智地检查参数,因此这并不罕见。

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());

每个语句都会被计算,但是表达式的值将是最后一个被计算的语句的值。

通过使用不寻常的类型转换来转换类型。虽然没有隐藏的特性,但是相当棘手。

例如:

如果你想知道编译器是如何存储浮点数的,试试这个:

uint32_t Int;
float flt = 10.5; // say


Int = *(uint32_t *)&flt;


printf ("Float 10.5 is stored internally as %8X\n", Int);

或者

float flt = 10.5; // say


printf ("Float 10.5 is stored internally as %8X\n", *(uint32_t *)&flt);

注意类型转换的巧妙使用。将变量的地址(here & flt)转换为所需类型(here (uint32 _ t *))并提取其内容(应用’*’)。

这也适用于表达的另一面:

*(float *)&Int = flt;

这也可以通过联合来实现:

typedef union
{
uint32_t Int;
float    flt;


} FloatInt_type;

我喜欢可变大小的结构:

typedef struct {
unsigned int size;
char buffer[1];
} tSizedBuffer;


tSizedBuffer *buff = (tSizedBuffer*)(malloc(sizeof(tSizedBuffer) + 99));


// can now refer to buff->buffer[0..99].

还有宏的偏移,现在在 ANSI C 中,但是我第一次看到它的时候它是一个魔法。它主要使用操作符 address-of (&)作为空指针的结构变量。

C 语言中我最喜欢的“隐藏”特性是 printf 中% n 的用法,它可以写回堆栈。通常 printf 根据格式字符串从堆栈中弹出参数值,但% n 可以将它们写回。

检查一下3.4.2 给你部分。可能会导致很多讨厌的漏洞。

Gcc 有许多我喜欢的 C 语言扩展,可以找到 给你。我最喜欢的一些是 函数属性。一个非常有用的示例是 format 属性。如果你定义了一个带有格式化字符串的自定义函数,就可以使用这个函数。如果您启用了这个函数属性,gcc 将检查您的参数,以确保格式化字符串和参数匹配,并根据需要生成警告或错误。

int my_printf (void *my_object, const char *my_format, ...)
__attribute__ ((format (printf, 2, 3)));

寄存器变量寄存器变量

我过去常常用 register关键字声明一些变量来帮助加快速度。这将提示 C 编译器使用 CPU 寄存器作为本地存储。这很可能不再是必要的,因为现代的 C 编译器会自动完成这项工作。

初始化结构为零

struct mystruct a = {0};

这将使所有结构元素归零。

结构分配很酷。许多人似乎没有意识到结构也是值,而且可以被分配,当一个简单的分配就可以解决问题时,没有必要使用 memcpy()

例如,考虑一些虚构的二维图形库,它可以定义一个类型来表示一个(整数)屏幕坐标:

typedef struct {
int x;
int y;
} Point;

现在,你可以做一些看起来“错误”的事情,比如写一个函数,创建一个从函数参数初始化的点,然后返回它,像这样:

Point point_new(int x, int y)
{
Point p;
p.x = x;
p.y = y;
return p;
}

这是安全的,只要(当然)使用 struct 赋值通过值复制返回值:

Point origin;
origin = point_new(0, 0);

通过这种方式,您可以编写非常干净和面向对象的代码,所有代码都使用普通的标准 C。

好吧,我从来没有用过它,我不知道我是否会向任何人推荐它,但我觉得这个问题将是不完整的,如果没有提到西蒙 Tatham 的 共同惯例的把戏。

编译时断言,如 已经讨论过了

//--- 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);

C99风格的变量参数宏,也就是

#define ERR(name, fmt, ...)   fprintf(stderr, "ERROR " #name ": " fmt "\n", \
__VAR_ARGS__)

就像是

ERR(errCantOpen, "File %s cannot be opened", filename);

在这里,我还使用了 stringize 操作符和 string 常量串联,这是我非常喜欢的其他特性。

节选 :

在本页中,您将找到 有趣的 C 语言编程 这些程序 列出的都是我有的 作为电子邮件从我的 朋友们,我在一些书中读到的一些 一些来自互联网,一些来自 我的 C 语言编程经验。

Http://www.gowrikumar.com/c/index.html

在初始化数组或枚举时,可以在初始化器列表的最后一项后面放置逗号。例如:

int x[] = { 1, 2, 3, };


enum foo { bar, baz, boom, };

这样做是为了在自动生成代码时不必担心去掉最后一个逗号。

Gcc (c)有一些可以启用的有趣特性,例如嵌套函数声明和 a?: b 形式的?: 操作符,它返回一个 if,a 不为 false。

假设您有一个具有相同类型成员的结构:

struct Point {
float x;
float y;
float z;
};

您可以将它的实例转换为浮点指针并使用数组索引:

Point a;
int sum = 0, i = 0;
for( ; i < 3; i++)
sum += ((float*)a)[i];

非常基本,但是在编写简洁的代码时非常有用。

C99有一些非常棒的任意顺序结构初始化。

struct foo{
int x;
int y;
char* name;
};


void main(){
struct foo f = { .y = 23, .name = "awesome", .x = -38 };
}

可变大小的结构,可以在其他地方的常见解析器库中看到。

struct foo
{
int a;
int b;
char b[1]; // using [0] is no longer correct
// must come at end
};


char *str = "abcdef";
int len = strlen(str);
struct foo *bar = malloc(sizeof(foo) + len);


strcpy(bar.b, str); // try and stop me!

在 gcc 里有三个不错的例子:

__FILE__
__FUNCTION__
__LINE__

像这样包装 malloc 和 realloc:

#ifdef _DEBUG
#define mmalloc(bytes)                  malloc(bytes);printf("malloc: %d\t<%s@%d>\n", bytes, __FILE__, __LINE__);
#define mrealloc(pointer, bytes)        realloc(pointer, bytes);printf("realloc: %d\t<%s@%d>\n", bytes, __FILE__, __LINE__);
#else //_DEBUG
#define mmalloc(bytes)                  malloc(bytes)
#define mrealloc(pointer, bytes)        realloc(pointer, bytes)

事实上,这是我的全套武器(The BailIfNot 是为 OO c 准备的) :

#ifdef _DEBUG
#define mmalloc(bytes)                  malloc(bytes);printf("malloc: %d\t<%s@%d>\n", bytes, __FILE__, __LINE__);
#define mrealloc(pointer, bytes)        realloc(pointer, bytes);printf("realloc: %d\t<%s@%d>\n", bytes, __FILE__, __LINE__);
#define BAILIFNOT(Node, Check)  if(Node->type != Check) return 0;
#define NULLCHECK(var)          if(var == NULL) setError(__FILE__, __LINE__, "Null exception", " var ", FATAL);
#define ASSERT(n)               if( ! ( n ) ) { printf("<ASSERT FAILURE@%s:%d>", __FILE__, __LINE__); fflush(0); __asm("int $0x3"); }
#define TRACE(n)                printf("trace: %s <%s@%d>\n", n, __FILE__, __LINE__);fflush(0);
#else //_DEBUG
#define mmalloc(bytes)                  malloc(bytes)
#define mrealloc(pointer, bytes)        realloc(pointer, bytes)
#define BAILIFNOT(Node, Check)  {}
#define NULLCHECK(var)          {}
#define ASSERT(n)               {}
#define TRACE(n)                {}
#endif //_DEBUG

下面是一些输出示例:

malloc: 12      <hash.c@298>
trace: nodeCreate <hash.c@302>
malloc: 5       <hash.c@308>
malloc: 16      <hash.c@316>
malloc: 256     <hash.c@320>
trace: dataLoadHead <hash.c@441>
malloc: 270     <hash.c@463>
malloc: 262144  <hash.c@467>
trace: dataLoadRecursive <hash.c@404>

我刚刚读了这个 文章。它有一些 C 和其他几种语言的“隐藏功能”。

面向对象 C 宏: 您需要一个构造函数(init)、一个析构函数(disstructor)、一个等号(equals)、一个复印机(copy)和一些用于实例化的原型(model)。

通过声明,您需要声明一个常量原型,以便从中进行复制和派生。然后你可以做 C_OO_NEW。如果需要,我可以发布更多的例子。LibCopper 是一个面向对象的大型 C 代码库,带有回调系统(如果您想看到一个正在使用的回调系统)

#define C_copy(to, from) to->copy(to, from)


#define true 1
#define false 0
#define C_OO_PROTOTYPE(type)\
void type##_init (struct type##_struct *my);\
void type##_dispose (struct type##_struct *my);\
char type##_equal (struct type##_struct *my, struct type##_struct *yours); \
struct type##_struct * type##_copy (struct type##_struct *my, struct type##_struct *from); \
const type type##__prototype = {type##_init, type##_dispose, type##_equal, type##_copy


#define C_OO_OVERHEAD(type)\
void (*init) (struct type##_struct *my);\
void (*dispose) (struct type##_struct *my);\
char (*equal) (struct type##_struct *my, struct type##_struct *yours); \
struct type##_struct *(*copy) (struct type##_struct *my, struct type##_struct *from);


#define C_OO_IN(ret, type, function, ...)       ret (* function ) (struct type##_struct *my, __VA_ARGS__);
#define C_OO_OUT(ret, type, function, ...)      ret type##_##function (struct type##_struct *my, __VA_ARGS__);


#define C_OO_PNEW(type, instance)\
instance = ( type *) malloc(sizeof( type ));\
memcpy(instance, & type##__prototype, sizeof( type ));


#define C_OO_NEW(type, instance)\
type instance;\
memcpy(&instance, & type ## __prototype, sizeof(type));


#define C_OO_DELETE(instance)\
instance->dispose(instance);\
free(instance);


#define C_OO_INIT(type)         void type##_init (struct type##_struct *my){return;}
#define C_OO_DISPOSE(type)      void type##_dispose (struct type##_struct *my){return;}
#define C_OO_EQUAL(type)        char type##_equal (struct type##_struct *my, struct type##_struct *yours){return 0;}
#define C_OO_COPY(type)         struct type##_struct * type##_copy (struct type##_struct *my, struct type##_struct *from){return 0;}

我喜欢 typeof ()操作符。它的工作原理类似于 sizeof () ,因为它是在编译时解析的。它不返回字节数,而是返回类型。当需要将变量声明为与其他变量相同的类型时,这非常有用,不管它是什么类型。

typeof(foo) copy_of_foo; //declare bar to be a variable of the same type as foo
copy_of_foo = foo; //now copy_of_foo has a backup of foo, for any type

这可能只是 GCC 扩展,我不确定。

当我第一次看到这个(隐藏的)特性时,我感到震惊的是 printf。此特性允许您使用变量来格式化格式说明符本身。寻找代码,你会看到更好的:

#include <stdio.h>


int main() {
int a = 3;
float b = 6.412355;
printf("%.*f\n",a,b);
return 0;
}

* 字符达到了这种效果。

为了清除输入缓冲区,您不能使用 fflush(stdin)。正确的方法如下: scanf("%*[^\n]%*c") 这将丢弃输入缓冲区中的所有内容。

使用 NaN 进行链式计算/返回错误:

//# include < stdint.h >
静态 uint64 _ t iNaN = 0xFFF800000000000;
Const double NaN = * (double *) & iNaN;//static NaN

内部函数可以返回 NaN 作为错误标志: 它可以安全地用于任何计算,并且结果总是 NaN。

注意: 测试 NaN 很棘手,因为 NaN! = NaN... 使用 isnan (x) ,或者自己卷。
如果 x 是 NaN,那么 x! = x 在数学上是正确的,但是有些编译器会对 x 进行优化

我是在15年多的 C 编程经验之后才发现这一点的:

struct SomeStruct
{
unsigned a : 5;
unsigned b : 1;
unsigned c : 7;
};

比特菲尔德!冒号后面的数字是成员所需要的位数,成员被打包到指定的类型中,所以如果 unsigned 为16位,上面的数字看起来如下所示:

xxxc cccc ccba aaaa

斯基兹

使用枚举进行编译时假设检查: 愚蠢的示例,但对于具有编译时可配置常量的库来说非常有用。

#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)
};

Intptr _ t 用于声明特定于 C99并在 stdint.h 中声明的指针类型的变量

GCC 中的 Lambda 函数(例如匿名函数) :

#define lambda(return_type, function_body) \
({ return_type fn function_body fn })

这可用作:

lambda (int, (int x, int y) { return x > y; })(1, 2)

扩展为:

({ int fn (int x, int y) { return x > y } fn; })(1, 2)

常数字符串串联

我很惊讶没有在答案中看到它,因为我所知道的所有编译器都支持它,但是许多程序员似乎忽略了它。有时候它真的很方便,而且不仅仅是在编写宏时。

我当前代码中的用例: 我在一个配置文件中有一个 #define PATH "/some/path/"(实际上它是由 makefile 设置的)。现在我想构建完整的路径,包括打开资源的文件名。它只是去:

fd = open(PATH "/file", flags);

而不是那些可怕的,但很普通的:

char buffer[256];
snprintf(buffer, 256, "%s/file", PATH);
fd = open(buffer, flags);

注意,常见的可怕解决方案是:

  • 三倍长
  • 更不容易阅读
  • 慢多了
  • 设置为任意的缓冲区大小限制(但是您必须使用更长的代码来避免在没有常量字符串连接的情况下出现这种情况)。
  • 使用更多的堆栈空间

我喜欢 __LINE____FILE__

在使用 sScanf 时,可以使用% n 来找出应该继续读取的位置:

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

我发现最近0位字段。

struct {
int    a:3;
int    b:2;
int     :0;
int    c:4;
int    d:3;
};

这将给出一个布局

000aaabb 0ccccddd

而不是没有: 0;

0000aaab bccccddd

宽度字段表示应该在下一个原子实体(char)上设置以下位字段

函数指针的大小不是标准的。至少在绑架勒索书里没有。尽管它讨论了其他类型指针的大小,但是(我认为)函数指针的 abc0是未定义行为的。

同时 sizeof也是一个编译时操作符,我看到很多人在网上论坛中询问 sizeof是一个函数还是一个操作符。

我看到的一个错误如下(一个简化的例子) :

int j;
int i;
j = sizeof(i++)

当在编译时计算 sizeof时,将不执行 i上的增量。程序员打算破解这两个操作,在 i上增量和在一个语句中计算 sizeof

C 语言中的运算符优先级决定关联顺序,而不是求值顺序。例如,如果有三个函数 fgh,每个函数返回一个 int,它们的表达式如下:

f() + g() * h()

C 标准没有给出这些函数求值顺序的规则。gh的结果在加入 f的结果之前要先加倍。如果函数共享状态并且计算依赖于这些函数的求值顺序,则可能导致错误。这可能导致可移植性问题。

Steve Webb 指出了 __LINE____FILE__宏。这让我想起在我以前的工作中,我是如何黑进他们的内存日志记录。

我在一个设备上工作,在这个设备上没有可用的端口来将日志信息从设备传递到用于调试的 PC 机。可以使用断点使用调试器暂停并了解程序的状态,但是没有关于系统跟踪的信息。

由于对调试日志的所有调用实际上都是一个全局宏,因此我们将该宏更改为将文件名和行号转储到一个全局数组中。该数组包含一系列文件名和行号,显示调用了哪些调试调用,从而提供了执行跟踪的公平概念(尽管不是实际的日志消息)。可以通过调试器暂停执行,将这些字节转储到本地文件中,然后使用脚本将这些信息映射到代码库。这是可能的,因为我们有严格的编码准则,所以我们可以在一个文件中对日志记录机制进行更改。

当比较变量和文字时,最好将文字放在 ==操作符的 左边中,以确保当您错误地使用赋值操作符时,编译器会给出一个错误。

if (0 == count) {
...
}

乍一看可能很奇怪,但是它可以省去一些麻烦(比如如果你不小心键入了 if (count = 0))。

在 VisualStudio 中,您可以突出显示自己定义的类型。

为此,在“ Comom7/IDE”文件夹中创建一个名为“ usertype.dat”的文件。该文件的内容应该是您想要突出显示的类型。例如:

//usertype.dat 的内容

int8_t
int16_t
int32_t
int64_t
uint8_t
uint16_t
uint32_t
uint64_t
float32_t
float64_t
char_t

经常被遗忘的 printf格式字符串中的 %n说明符有时可能非常实用。% n 返回 printf 格式化其输出时使用的假想游标的当前位置。

int pos1, pos2;
char *string_of_unknown_length = "we don't care about the length of this";


printf("Write text of unknown %n(%s)%n text\n", &pos1, string_of_unknown_length, &pos2);
printf("%*s\\%*s/\n", pos1, " ", pos2-pos1-2, " ");
printf("%*s", pos1+1, " ");
for(int i=pos1+1; i<pos2-1; i++)
putc('-', stdout);
putc('\n', stdout);

将有以下输出

Write text of unknown (we don't care about the length of this) text
\                                      /
--------------------------------------

虽然有点做作,但在编写漂亮的报告时还是有一些用处的。

在开关中使用 while (0)如何,这样您就可以使用 break: -)这样的继续语句了

void sw(int s)
{
switch (s) while (0) {
case 0:
printf("zero\n");
continue;
case 1:
printf("one\n");
continue;
default:
printf("something else\n");
continue;
}
}