C 語言有什麼奇技淫巧?
== C語言的黑魔法 ==
黑魔法一、引用賈揚清的回答:C有一個鮮為人知的運算符叫」趨向於」, 寫作「--&>」。比如說如果要實現一個倒數的程序,我們可以定義一個變數x,然後讓它趨向於0...
C++有另一個更鮮為人知的運算符叫做「快速趨向於」,比如同樣是從10到0,這裡這麼寫
#include &
int main(void)
{
int x = 10;
while (0 &<---- x) {
printf("%d ", x);
}
return 0;
}
會列印出:
8 6 4 2
黑魔法二、以下等式皆成立:
a[5] == 5[a];
"ABCD"[2] == 2["ABCD"] == "C";
因為C語言的底層都是指針,數組的實現也是。對於compiler來說,它們木有區別:
*(a + 5) == *(5 + a)
黑魔法三、問:以下這個語句是幹啥的?
!ErrorHasOccured() ??!??! HandleError();
它叫trigraph,以上會被翻譯為
!ErrorHasOccured() || HandleError();
黑魔法四、
下面這個列印是否讓你驚奇?#include &
int main(void)
{
int x = 5;
printf("%d and ", sizeof(x++)); // note 1
printf("%d
", x); // note 2
return 0;
}
它會列印出:
4 and 5
因為 sizeof 是編譯時行為,運行時不會執行
—————————————————
以上回答皆引用自:Highest Voted "c" Questions—————————————————UPDATE 2015.1.8
黑魔法五、(代碼來自參與過的開源工程ovs,OpenFlow的業界標杆)#define OFPACTS
/* Output. */
OFPACT(OUTPUT, ofpact_output, ofpact, "output")
OFPACT(GROUP, ofpact_group, ofpact, "group")
#define OFPACT(ENUM, STRUCT, MEMBER, NAME)
BUILD_ASSERT_DECL(offsetof(struct STRUCT, ofpact) == 0);
enum { OFPACT_##ENUM##_RAW_SIZE
= (offsetof(struct STRUCT, MEMBER)
? offsetof(struct STRUCT, MEMBER)
: sizeof(struct STRUCT)) };
static inline struct STRUCT *
ofpact_get_##ENUM(const struct ofpact *ofpact)
{
ovs_assert(ofpact-&>type == OFPACT_##ENUM);
return ALIGNED_CAST(struct STRUCT *, ofpact);
}
OFPACTS
#undef OFPACT
看懂以上代碼的功能了嗎?它定義了一些欄位,然後再通過宏來批量生成函數和enum,狂拽酷炫!
學名叫做x macro,是節省冗餘代碼利器,好處是非常好用,跟機關槍一樣;壞處是懂的人不多,大家看到一個沒有被索引的ofpact_get_GROUP很容易就進入痴呆狀態。
完整版的黑魔法五可以點這裡 cowry/x_macro.c at master · geekan/cowry · GitHub——————————- 佔位待更新,想更新Qemu的狂拽酷炫的宏對象實現,有人支持嗎~
——————————
UPDATE 2015/7/22剛把廢棄已久的博客架起來,發現以前還寫過一點有意思的文章,歡迎延伸閱讀:- C語言的奇技淫巧
補充一個,XOR linked list原理很簡單,利用C的按位異或只用一個位元組的指針信息就實現雙向鏈表除了開頭和結尾,每個節點保存其相鄰節點的地址的異或結果,正向遍歷時用當前節點地址欄位中保存的值異或後一個,反向遍歷時異或前一個。
而開頭節點存下一個節點地址,尾節點保存前一節點地址。
這樣一來,用一個介面就能實現鏈表雙向遍歷,還比雙鏈表節省空間Duff"s device!初看以為不能通過編譯,後來以為是濫用編譯器,但其實是完全符合C標準的,而且真實項目里有用。Google 上搜「duff"s device filetype:c」和「duff"s device filetype:h」都能找到很多。LLVM 還專門有個用例測試對 Duff"s device 的支持:SingleSource/Regression/C/DuffsDevice.c,還很開心噠地說「Guess what, it does. :)」
(更新刪除:比如Chrome里就有:https://code.google.com/p/chromium/codesearch#chromium/src/v8/test/mjsunit/unicode-test.jsq=Duff 。感謝 @莫金雨 指正,這段代碼並不是真正被使用的。)
send(to, from, count)
register short *to, *from;
register count;
{
register 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);
}
}
更多詳情可參考:Duff"s device
________________________________補充一個:_(__, __);
___[__];
____=__;
這不是字元畫,不是表情,是合法的C語言語句。因為下劃線是合法的標識符。
PS. 上面的分割線也是個合法的標識符。C有一個鮮為人知的運算符叫」趨向於」, 寫作「--&>」。比如說如果要實現一個倒數的程序,我們可以定義一個變數x,然後讓它趨向與0:
#include &
int main(int argc, char** argv) {
int x = 10;
while (x --&> 0) {
printf("%d ", x);
}
return 0;
}
會列印出:
9 8 7 6 5 4 3 2 1 0
----------------
好吧我承認我是來惡搞的。。。不過程序真的能run。評論里說我應該加上參考文獻,所以去找了一下我最開始看到這個的stackoverflow - 這個鏈接裡面還有更多腦洞大開的解釋。。。[1] c++ - What is the name of the "--&>" operator?除了@賈揚清的回答中提到的趨向於運算符,C語言還有另一個更加鮮為人知的運算符,叫做蝌蚪運算符(tadpole operator),用於實現單目的加一、減一運算。
語法 含義 助記
-~y y + 1 蝌蚪游向一個值讓它變大
~-y y - 1 蝌蚪離開一個值讓它變小
x = (y + 1) % 10;
x = (y + 1) * (z - 1);
x = (double)(f(y) + 1);
變為
x = -~y % 10;
x = -~y * ~-z;
x = (double)-~f(y);
減少了括弧的使用,使代碼更簡單。
-----------參考文獻:來自率先聲明支持這一運算符的Visual C++編譯器的文章:New C++ experimental feature: The tadpole operators有時候在一些比較特殊的結構體里, 結構體的元素大小和基本類型的大小不符, 就可以用位域來表示這個結構體, 就可以直接操作這些只佔幾個bit的元素, 比如全局描述符號表(GDT)可以用位域這樣表示, 用冒號表示變數所佔的位數:
struct desc_struct {
union {
struct {
unsigned int a;
unsigned int b;
};
struct {
u16 limit0;
u16 base0;
unsigned base1: 8, type: 4, s: 1, dpl: 2, p: 1;
unsigned limit: 4, avl: 1, l: 1, d: 1, g: 1, base2: 8;
};
};
} __attribute__((packed));
舉個更簡單的例子, 在顯卡的文本模式里, 一個字元的數據結構是這樣的:
如果不用位域, 直接用一個 unsigned short 來表示一個黑底白字的字元"A", 要這樣寫:
unsigned short char_A = "A"|((COL_BLACK&<&<4)|COL_WHITE)&<&<8;
如果用位域聲明一個結構體, 就可以這樣寫:
struct vga_char{
char _char: 8;
char f_color: 4;
char b_color: 4;
};
struct vga_char char_A;
char_A._char = "A";
char_A.f_color = COL_WHITE;
char_A.b_color = COL_BLACK;
代碼是長了不少, 不過直觀多了.
我來發一對CP。
youmu.c - 魂魄 妖夢yuyuko.c - 西行寺 幽々子將她們的代碼編譯之後輸出就是對方的源代碼!雖然說只是一個Quine。(′?ω?`)還有一個在kernel中比較常見的X macros.#define FOO(x) ...
#include "path/to/your/file"
#undef FOO
搭順風車來一發black magic腦洞大開的 Cello ? High Level Programming C將C語言變成支持動態類型的函數式編程語言
一、宏相關的條件編譯防止頭文件被重複包含,這個很常用,基本是常識了。。
#ifndef _XXX_H
#define _XXX_H
#endif
另外可以實現不同系統下代碼的轉換(比如不同系統下引入包含不同的庫文件)
編譯時斷言:在編譯時就能夠進行條件檢查的斷言,而不是在運行時進行。下面是個Linux Kernel的例子/* Force a compilation error if condition is true */
#define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2*!!(condition)]))
tagged pointer:在動態語言中,和位運算結合,實現Int類型的unbox(這個有點偏。。
enum type { pair, string, vector, ... };
typedef struct value *SCM;
struct value {
enum type type;
union {
struct { SCM car, cdr; } pair;
struct { int length; char *elts; } string;
struct { int length; SCM *elts; } vector;
...
} value;
};
#define POINTER_P(x) (((int) (x) 7) == 0)
#define INTEGER_P(x) (! POINTER_P (x))
#define GET_INTEGER(x) ((int) (x) &>&> 3)
#define MAKE_INTEGER(x) ((SCM) (((x) &<&< 3) | 1))
其他:宏列表,用內建宏調試(如_FLLE_,_LINE_),封裝基本數據類型方便跨平台等。。
據說宏處理器是圖靈完備的。。。二、數據結構相關匿名數據結構:匿名數組、匿名結構體等。下面我們來實現Lisp的List。。int main() {
struct mylist {
int a;
struct mylist* next;
};
#define cons(x, y) (struct mylist[]){{x, y}}
struct mylist *list = cons(1, cons(2, cons(3, NULL)));
struct mylist *p = list;
while(p != 0) {
printf("%d
", p-&>a);
p = p -&> next;
}
}
static struct usb_driver usb_storage_driver = {
.owner = THIS_MODULE,
.name = "usb-storage",
.probe = storage_probe,
.disconnect = storage_disconnect,
.id_table = storage_usb_ids,
};
結構體+指針:這個花樣就多了去了。。
內存對齊:用在優化中
三、函數相關
setjmp/longjmp實現協程、異常等都靠它函數指針
函數指針可以實現高階函數、模擬簡單的閉包。gcc的C擴展支持嵌套函數. clang的C好像還有閉包..#include &
int main() {
int swap (int *a, int *b) {
int c;
c = *a;
*a = *b;
*b = c;
return 0;
}
int first = 12, second = 34;
printf("f is %d and s is %d
", first, second);
swap(first, second);
printf("f is %d and s is %d
", first, second);
return 0;
}
--以後補充--
OS Kernel,遊戲引擎,編譯器之類的會用到不少黑魔法。。把一維數組轉化成大小不超過一維數組大小的我想要的形狀的 N 維數組。
這裡以將一個長度為 20 的一維 int 數組轉化成 5 行 4 列的二維 int 數組為例:
int array[20], (*parray)[4] = (int(*)[4])array, (*parray2)[5][4] = (int (*)[5][4])array;
array[4] = 200;
printf("%d
",(*(parray+1))[0]);
printf("%d
",(*parray2)[1][0]);
以上代碼輸出結果為:
200
200
這裡相當於把一個長度為 20 的一維數組轉換成 5 行 4 列的二維數組來使用。
在 C 語言裡面,指針其實有兩個含義:
- 指向目標的內存地址。
- 目標在內存中的大小。
parray 存放的是一個 int 數組的地址,這個數組在內存中佔用的空間為 4 * sizeof(int)。
所以,(parray+1) 的意思是將指向的地址偏移 4 * sizeof(int) 後的內存地址,而這個內存地址剛好就是數組 array 的第 5 個元素的內存地址。
因此,我們就可以通過 parray 以對待二維數組的方式操作一個一維數組 array 了。
parray2 道理大體上相同。
這裡解釋一下每一個步驟。
- int array[20] 定義了一個在 Stack 上的數組。
- (*parray)[4] 定義了一個指向長度為 4 的數組的指針。
- parray = (int(*)[4]) array[0] 取出 array 的地址(現在地址的類型是 int(*)[20])轉換成 int(*)[4] 賦給 parray。
- (*parray2)[5][4] 定義了一個指向 5 行 4 列二維數組的指針。
- parray2 = (int (*)[5][4]) array 取出 array 的地址(現在地址的類型是 int(*)[20])轉換成 int(*)[5][4] 賦給 parray2。
- array[4] = 200 將 array 的第 5 個元素的值設為 200。
- (*(parray+1))[0] 首先將指針偏移 4 個 int 的長度,取出指針指向的數組,取出數組的第一個值 200。
- (*parray2)[1][0]) 首先取出指針指向的數組,取出數組第 2 行第 1 列的值 200。
用switch實現yield語義
1.
struct array_on_heap{ int len; int data[0];};2.(void) (a == b)數據類型強轉算不算?將0到1000的浮點數強制類型轉換然後生成一幅圖像:
#include &
#include &
#include &
#define DIM 1000
void pixel_write(int,int);
FILE *fp;
int main()
{
fp = fopen("image.ppm","wb");
if (!fp)
{
return -1;
}
fprintf(fp, "P6
%d %d
255
", DIM, DIM);
for(int j=0;j&
IOCCC還有更多令人膝蓋一軟的例子:Previous IOCCC Winners隨便搜了一下還真沒找到自動生成ASCII art的c code的工具。。。如果是手寫的那真太厲害了。。
#define XXX(X) do {...} while(0)
摘自《C專家編程》 (如果你正在學習C/C++,建議你多讀幾遍這本書。網上有電子版,我就不給鏈接了。)
根據位模式構建圖形圖標(icon)或者圖形(glyph),是一種小型的位模式映射於屏幕產生的圖像。一個位代表圖像上的一個像素。如果一個位被設置,那麼它所代表的像素就是「亮」的。如果一個位被清除,那麼它所代表的像素就是「暗」的。所以,一系列的整數值能夠用於為圖像編碼。
類似Iconedit這樣的工具就是用於繪圖的,他們所輸出的是一個包含一系列整型數的ASCII文件,可以被一個窗口程序所包含。它所存在的問題是程序中的圖標只是一串十六進位數。
在C語言中,典型的16X16的黑白圖形可能如下:
static unsigned short stopwatch[] = {
0x07C6,
0x1FF7,
0x383B,
0x600C,
0x600C,
0xC006,
0xC006,
0xDF06,
0xC106,
0xC106,
0x610C,
0x610C,
0x3838,
0x1FF0,
0x07C0,
0x0000
};
正如所看到的那樣,這些C語言常量並未有提供有關圖形實際模樣的任何線索。
這裡有一個驚人的#define定義的優雅集合,允許程序建立常量使它們看上去像是屏幕上的圖形。
#define X )*2+1
#define _ )*2
#define s ((((((((((((((((0 /* For building glyphs 16 bits wide */
定義了它們之後,只要畫所需要的圖標或者圖形等,程序會自動創建它們的十六進位模式。使用這些宏定義,程序的自描述能力大大加強,上面這個例子可以轉變為:
static unsigned short stopwatch[] =
{
s _ _ _ _ _ X X X X X _ _ _ X X _ ,
s _ _ _ X X X X X X X X X _ X X X ,
s _ _ X X X _ _ _ _ _ X X X _ X X ,
s _ X X _ _ _ _ _ _ _ _ _ X X _ _ ,
s _ X X _ _ _ _ _ _ _ _ _ X X _ _ ,
s X X _ _ _ _ _ _ _ _ _ _ _ X X _ ,
s X X _ _ _ _ _ _ _ _ _ _ _ X X _ ,
s X X _ X X X X X _ _ _ _ _ X X _ ,
s X X _ _ _ _ _ X _ _ _ _ _ X X _ ,
s X X _ _ _ _ _ X _ _ _ _ _ X X _ ,
s _ X X _ _ _ _ X _ _ _ _ X X _ _ ,
s _ X X _ _ _ _ X _ _ _ _ X X _ _ ,
s _ _ X X X _ _ _ _ _ X X X _ _ _ ,
s _ _ _ X X X X X X X X X _ _ _ _ ,
s _ _ _ _ _ X X X X X _ _ _ _ _ _ ,
s _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
};
顯然,與前面的代碼相比,它的意思更為明顯。標準的C語言具有八進位、十進位和十六進位常量,但沒有二進位常量,否則的話倒是一種更為簡單的繪製圖形模式的方法。
說個實用點的,含有函數指針的struct實現狀態機和消息地圖
/** @note Sample code, Unfinished*/
#define FOREVER 1
typedef void (*Handler)(void);
typedef unsigned char Code;
tpyedef struct
{
Code,
Handler
} State;
#define STATE_A 0
void A_Handler(void);
#define STATE_B 1
void B_Handler(void);
State MyStateMap[] =
{
{STATE_A,A_Handler},
{STATE_B,B_Handler}
}
#define SIZE(x) sizeof(x)/sizeof(State)
void SearchMap(Code Status)
{
for(unsigned char n = 0; n &< SIZE(MyStateMap); ++n)
if(MyStateMap[n].Code == Status)
{
MyStateMap[n].Handler();
break;
}
}
int main()
{
Code MyStatus;
//! Do some Init
while(FOREVER)
{
//! Do something
SearchMap(MyStatus);
}
return 0;
}
來自 Linux kernel
#define is_power_of_2(n) ((n) != 0 ((n) ((n) - 1)) == 0)不知道這個「奇技淫巧」是怎麼定義的,但在用C設計模塊介面的時候,有很多非常實用小tricks還是值得提一下的。...Redis里的sds(簡易動態字元串庫)
struct sdshdr {
int len;
int free;
char buf[];
};
sds sdsnewlen(const void *init, size_t initlen) {
struct sdshdr *sh;
if (init) {
sh = zmalloc(sizeof(struct sdshdr)+initlen+1);
} else {
sh = zcalloc(sizeof(struct sdshdr)+initlen+1);
}
if (sh == NULL) return NULL;
sh-&>len = initlen;
sh-&>free = 0;
if (initlen init)
memcpy(sh-&>buf, init, initlen);
sh-&>buf[initlen] = " ";
return (char*)sh-&>buf;
//返回了char *
}
而新申請的sds返回了char *,而不是struct sdshdr *。直接使用的也是char *。這樣做的目的是為了兼容一些類似strcmp、strcat的庫函數。
而要使用到sds的信息時,只需要將字元串地址減去sds的大小就可以的到sds的地址。例如:static inline size_t sdslen(const sds s) {
struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr))); //獲取sds的地址
return sh-&>len; //返回長度
}
關於宏:
/// rief Provides a simple uniform namespace for tokens from all C languages.
enum TokenKind : unsigned short {
#define TOK(X) X,
#include "clang/Basic/TokenKinds.def"
NUM_TOKENS
};
/// rief Provides a namespace for preprocessor keywords which start with a
/// "#" at the beginning of the line.
enum PPKeywordKind {
#define PPKEYWORD(X) pp_##X,
#include "clang/Basic/TokenKinds.def"
NUM_PP_KEYWORDS
};
/// rief Provides a namespace for Objective-C keywords which start with
/// an "@".
enum ObjCKeywordKind {
#define OBJC1_AT_KEYWORD(X) objc_##X,
#define OBJC2_AT_KEYWORD(X) objc_##X,
#include "clang/Basic/TokenKinds.def"
NUM_OBJC_KEYWORDS
};
// C99 6.4.6: Punctuators.
PUNCTUATOR(l_square, "[")
PUNCTUATOR(r_square, "]")
PUNCTUATOR(l_paren, "(")
PUNCTUATOR(r_paren, ")")
PUNCTUATOR(l_brace, "{")
PUNCTUATOR(r_brace, "}")
PUNCTUATOR(period, ".")
PUNCTUATOR(ellipsis, "...")
PUNCTUATOR(amp, "")
PUNCTUATOR(ampamp, "")
PUNCTUATOR(ampequal, "=")
PUNCTUATOR(star, "*")
PUNCTUATOR(starequal, "*=")
PUNCTUATOR(plus, "+")
PUNCTUATOR(plusplus, "++")
PUNCTUATOR(plusequal, "+=")
PUNCTUATOR(minus, "-")
PUNCTUATOR(arrow, "-&>")
PUNCTUATOR(minusminus, "--")
PUNCTUATOR(minusequal, "-=")
PUNCTUATOR(tilde, "~")
PUNCTUATOR(exclaim, "!")
PUNCTUATOR(exclaimequal, "!=")
TokenKinds.def其實就是個頭文件, 為了讓這種大量的同類內容可以被一起控制, 使用宏展開來寫對應的代碼真是再合適不過了.
閉包:clang里存在對於C語言的閉包擴展, 叫做block, 語法和函數指針的定義類似.void testBlock(void (^block)(int arg1)) {
block(0);
}
推薦閱讀:
※如何快速便捷地打小括弧?
※c語言里的char大小到底是4還是1?
※為什麼不都用memmove代替memcpy?
※反編譯工具能反編譯出注釋嗎?
※程序的靜態存儲區,動態存儲區和堆以及棧的關係是什麼?
TAG:C編程語言 | X編程語言有什麼奇技淫巧 |