141

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

4

56 回答 56

115

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();
     ...
}
于 2008-09-25T14:08:29.740 回答
77
int8_t
int16_t
int32_t
uint8_t
uint16_t
uint32_t

这些是标准中的一个可选项目,但它必须是一个隐藏的特性,因为人们在不断地重新定义它们。我工作过的一个代码库(现在仍然这样做)有多个重新定义,所有这些都具有不同的标识符。大多数时候它与预处理器宏有关:

#define INT16 short
#define INT32  long

等等。它让我想把我的头发拉出来。 只需使用该死的标准整数类型定义!

于 2008-09-25T14:31:30.573 回答
72

逗号运算符没有被广泛使用。它当然可以被滥用,但它也可能非常有用。这种用法是最常见的:

for (int i=0; i<10; i++, doSomethingElse())
{
  /* whatever */
}

但是您可以在任何地方使用此运算符。观察:

int j = (printf("Assigning variable j\n"), getValueFromSomewhere());

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

于 2008-09-25T14:37:22.057 回答
63

将结构初始化为零

struct mystruct a = {0};

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

于 2008-10-01T00:34:59.980 回答
62

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

然后是标准库中隐藏的宝石,例如 qsort()、bsearch()、strpbrk()、strcspn() [后两者对于实现 strtok() 替换很有用]。

C 的一个错误特征是有符号算术溢出是未定义的行为(UB)。因此,每当您看到诸如 x+y 之类的表达式都是有符号整数时,它可能会溢出并导致 UB。

于 2008-09-25T10:29:18.160 回答
52

多字符常量:

int x = 'ABCD';

这设置x0x41424344(或0x44434241,取决于架构)。

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

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

如果您正在查看原始内存转储并且需要确定枚举的值而无需查找它,这会使它变得更加简单。

于 2008-09-25T11:21:37.427 回答
44

我从未使用过位域,但它们对于超低级的东西听起来很酷。

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)


合并了Aaronleppie的评论,谢谢大家。

于 2008-09-25T10:44:09.560 回答
37

像Duff's Device这样的交错结构:

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);
    }
}
于 2008-09-25T09:12:30.093 回答
37

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 示例) .

于 2008-09-25T09:20:57.747 回答
33

我非常喜欢在 C99 中添加的指定初始化程序(并且在 gcc 中支持了很长时间):

#define FOO 16
#define BAR 3

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

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

于 2008-09-25T14:15:22.843 回答
28

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

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

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

于 2009-06-22T04:01:19.323 回答
27

匿名结构和数组是我最喜欢的。(参见http://www.run.montefiore.ulg.ac.be/~martin/resources/kung-f00.html

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

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

于 2008-09-25T10:16:48.597 回答
24

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

于 2008-09-25T09:10:07.260 回答
24

gcc 有许多我喜欢的 C 语言扩展,可以在这里找到。我最喜欢的一些是函数属性。一个非常有用的例子是格式属性。如果您定义了一个采用 printf 格式字符串的自定义函数,则可以使用它。如果您启用此函数属性,gcc 将对您的参数进行检查,以确保您的格式字符串和参数匹配,并根据需要生成警告或错误。

int my_printf (void *my_object, const char *my_format, ...)
            __attribute__ ((format (printf, 2, 3)));
于 2008-09-29T00:27:06.007 回答
24

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

#include <stdio.h>

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

* 字符实现了这个效果。

于 2009-06-22T11:10:14.113 回答
19

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

//--- 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);
于 2008-10-22T15:01:23.133 回答
16

常量字符串连接

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

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

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

而不是可怕的,但很常见:

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

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

  • 三倍长
  • 不太容易阅读
  • 慢得多
  • 将其设置为任意缓冲区大小限制时功能较弱(但您将不得不使用更长的代码来避免这种情况,而没有常量字符串的连接)。
  • 使用更多的堆栈空间
于 2010-05-08T08:22:58.173 回答
15

好吧,我从未使用过它,我不确定我是否会向任何人推荐它,但我觉得如果不提及 Simon Tatham 的协同程序技巧,这个问题将是不完整的。

于 2008-10-17T08:54:17.637 回答
12

结构分配很酷。许多人似乎没有意识到结构体也是值,并且可以被赋值,没有必要使用memcpy(),当一个简单的赋值就可以了。

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

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

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

于 2008-10-16T09:56:18.260 回答
12

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

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

enum foo { bar, baz, boom, };

这样做是为了如果您自动生成代码,则无需担心消除最后一个逗号。

于 2009-06-11T11:14:49.373 回答
10

奇怪的向量索引:

int v[100]; int index = 10; 
/* v[index] it's the same thing as index[v] */
于 2008-09-25T09:30:28.207 回答
9

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

通常,当您在编译器使用的任何 C 标准的锋利边缘上运行时,不鼓励隐藏功能或语言技巧。许多这样的技巧在一个编译器到另一个编译器中都不起作用,并且这些类型的功能通常会从给定制造商的编译器套件的一个版本到另一个版本失败。

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

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

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

于 2008-09-25T09:26:19.873 回答
9

使用 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 );
}

此代码将输出:

你好
ROFL
于 2010-10-31T11:35:20.527 回答
8

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

于 2008-09-25T09:06:39.590 回答
8

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

在此处查看第 3.4.2 节。会导致很多讨厌的漏洞。

于 2008-09-26T20:18:15.857 回答
8

Gcc (c) 有一些您可以启用的有趣功能,例如嵌套函数声明,以及 ?: 运算符的 a?:b 形式,如果 a 不为假,则返回 a。

于 2009-06-11T22:19:12.537 回答
8

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

#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)
};
于 2009-11-11T13:42:11.187 回答
8

我最近发现了 0 个位域。

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

这将给出一个布局

000aaabb 0ccccddd

而不是没有 :0;

0000aaab bccccddd

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

于 2010-11-26T16:06:32.350 回答
7

C99 风格的变量参数宏,又名

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

这将像

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

这里我还使用了 stringize 运算符和字符串常量连接,其他我非常喜欢的功能。

于 2008-10-22T18:00:24.647 回答
6

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

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

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

在调用此例程之前,您必须确保 extraPadding 是一个合理的值,否则您最终会破坏堆栈。在调用 malloc 或任何其他内存分配技术之前,您必须彻底检查参数,所以这并不罕见。

于 2008-09-25T14:23:20.423 回答
5

我喜欢你可以制作的可变大小的结构:

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 中的 offsetof 宏,但我第一次看到它时却是一个魔法。它基本上使用地址运算符 (&) 将空指针重铸为结构变量。

于 2008-09-26T06:56:18.197 回答
5

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)
于 2010-05-05T13:39:51.353 回答
5

我喜欢__LINE____FILE__。见这里: http: //gcc.gnu.org/onlinedocs/cpp/Standard-Predefined-Macros.html

于 2010-05-26T15:38:24.310 回答
4

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

于 2009-06-22T14:44:48.157 回答
3

早期版本的 gcc 会在源代码中遇到“#pragma”时尝试运行游戏。另请参见此处

于 2008-09-25T09:14:22.443 回答
3

我曾经在一段代码中看到过这个,然后问它做了什么:


hexDigit = "0123456789abcdef"[someNybble];

另一个最喜欢的是:


unsigned char bar[100];
unsigned char *foo = bar;
unsigned char blah = 42[foo];
于 2008-09-25T11:57:47.747 回答
3

使用不寻常的类型转换进行类型转换。虽然没有隐藏功能,但它非常棘手。

例子:

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

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

注意类型转换的巧妙使用。将变量的地址(此处为 &flt)转换为所需的类型(此处为 (uint32_t * ))并提取其内容(应用 '*')。

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

*(float *)&Int = flt;

这也可以使用联合来完成:

typedef union
{
  uint32_t Int;
  float    flt;

} FloatInt_type;
于 2008-09-25T19:10:38.033 回答
3

我是在 15 年以上的 C 编程之后才发现的:

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

位域!冒号后面的数字是成员需要的位数,成员被打包成指定的类型,所以如果 unsigned 是 16 位,上面的内容如下所示:

xxxc cccc ccba aaaa

斯基兹

于 2009-10-28T10:28:57.673 回答
2

不是真正的隐藏功能,但在我看来,它就像巫毒教一样,我第一次看到这样的东西:


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

    return;
    data = NULL;
}

这种构造的原因是,如果你用 -Wextra 编译它并且没有“data = NULL;”-line,gcc 将发出关于未使用参数的警告。但是对于这条无用的行,您不会收到警告。

编辑:我知道还有其他(更好的)方法可以防止这些警告。只是觉得很奇怪,我第一次看到这个。

于 2008-09-25T12:49:09.260 回答
2

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

于 2010-03-24T10:48:34.317 回答
2

Steve Webb指出了__LINE__and__FILE__宏。它让我想起了在我之前的工作中,我是如何破解他们的内存日志记录的。

我正在使用没有端口可将日志信息从设备传递到用于调试的 PC 的设备。可以使用断点停止并使用调试器了解程序的状态,但没有关于系统跟踪的信息。

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

于 2011-01-07T04:38:18.783 回答
2

在将变量与文字进行比较时,最好将文字放在运算符的左侧==以确保当您错误地使用赋值运算符时编译器会出错。

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

乍一看可能看起来很奇怪,但它可以避免一些头痛(比如如果你碰巧输入if (count = 0)错误)。

于 2012-01-15T04:15:30.273 回答
1

注册变量

我曾经用register关键字声明一些变量来帮助加快速度。这将提示 C 编译器使用 CPU 寄存器作为本地存储。这很可能不再需要,因为现代 C 编译器会自动执行此操作。

于 2008-09-29T00:39:33.277 回答
1

摘录

在此页面中,您将找到一系列有趣的 C 编程问题/谜题,列出的这些程序是我从朋友那里收到的电子邮件转发,一些我在一些书籍中读到的,一些来自互联网,还有一些来自我的 C 编码经验。

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

于 2009-01-13T11:26:43.273 回答
1

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

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

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

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

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

于 2009-06-21T23:29:01.030 回答
1

这是 gcc 中的三个不错的:

__FILE__ 
__FUNCTION__
__LINE__
于 2009-06-22T06:24:02.270 回答
1

函数指针的大小不是标准的。至少在 K&R 书中没有。即使它谈论其他类型指针的大小,但(我认为)sizeof函数指针是未定义的行为。

也是sizeof编译时运算符,我看到很多人在在线论坛上询问sizeof是函数还是运算符。

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

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

不会像在编译时评估的i那样执行增量。sizeof程序员打算在一个语句中破解这两种操作,增量i和计算。sizeof

C 中的运算符优先级控制关联顺序而不是评估顺序。例如,如果您有三个函数f, gh每个函数都返回一个int,它们的表达式如下:

f() + g() * h()

C 标准没有给出关于这些函数的评估顺序的规则。的结果gh将在添加 的结果之前相乘f。如果函数共享状态并且计算取决于这些函数的评估顺序,这可能会导致错误。这可能会导致可移植性问题。

于 2011-01-07T04:19:06.637 回答
0

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

结构体
{
  诠释一个;
  诠释 b;
  字符 b[1]; // 使用 [0] 不再正确
             // 必须在结尾
};

字符 *str = "abcdef";
int len = strlen(str);
结构 foo *bar = malloc(sizeof(foo) + len);

strcpy(bar.b, str); // 试着阻止我!
于 2009-06-22T05:02:00.537 回答
0

像这样包装 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)

事实上,这是我的全部武器(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>
于 2009-06-22T06:28:39.797 回答
0

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

于 2009-06-22T06:28:55.147 回答
0

面向对象的 C 宏:您需要一个构造函数 (init)、一个析构函数 (dispose)、一个相等 (equal)、一个复制器 (copy),以及一些用于实例化的原型 (prototype)。

使用声明,您需要声明一个常量原型以进行复制和派生。然后你可以做C_OO_NEW。如果需要,我可以发布更多示例。LibPurple 是一个带有回调系统的大型面向对象的 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;}
于 2009-06-22T06:38:22.383 回答
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 扩展,我不确定。

于 2009-06-22T07:49:12.833 回答
0

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

//#include <stdint.h>
static uint64_t iNaN = 0xFFF8000000000000;
const double NaN = *(double *)&iNaN; // 安静的 NaN

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

注意:测试 NaN 很棘手,因为 NaN != NaN... 使用 isnan(x),或者自己滚动。
如果 x 为 NaN,则 x!=x 在数学上是正确的,但往往会被某些编译器优化

于 2009-06-22T16:29:08.947 回答
0

格式字符串中经常被遗忘的%n说明printf符有时非常实用。%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
                      \                                      /
                       --------------------------------------

当然有点做作,但在制作漂亮的报告时可以有一些用途。

于 2012-01-19T17:18:53.260 回答
0

在 switch 中使用 while(0) 怎么样,这样你就可以使用像 break 这样的 continue 语句 :-)

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;
    }
}
于 2012-02-09T23:29:45.937 回答
-1

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

为此,在文件夹“Commom7/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
于 2012-01-18T01:30:03.200 回答