C基礎 那些年用過的奇巧淫技

来源:http://www.cnblogs.com/life2refuel/archive/2016/04/22/5422778.html
-Advertisement-
Play Games

引言 - 為尋一顆明星 { 風 : http://music.163.com/#/song?id=5276735 } 前言 - 有點扯 C基本是程式生涯的入門語言. 雖說簡單, 但已經斷層了. 估計是不合時宜吧. 工作中也就在網路層框架會看見部分C的影子. 自己用C開發久了, 發現C一個弊端是 當一 ...


引言 - 為尋一顆明星

  

為要尋一顆明星
        徐志摩 1924年12月1日《晨報六周年紀念增刊》 我騎著一匹拐腿的瞎馬, 向著黑夜裡加鞭;—— 向著黑夜裡加鞭, 我跨著一匹拐腿的瞎馬。
// 我沖入這黑綿綿的昏夜, 為要尋一顆明星;—— 為要尋一顆明星, 我沖入這黑茫茫的荒野。// 累壞了,累壞了我胯下的牲口, 那明星還不出現;—— 那明星還不出現, 累壞了,累壞了馬鞍上的身手。// 這回天上透出了水晶似的光明, 荒野里倒著一隻牲口, 黑夜裡躺著一具屍首。—— 這回天上透出了水晶似的光明!//

{   :  http://music.163.com/#/song?id=5276735  }

 

前言 - 有點扯

  C基本是程式生涯的入門語言. 雖說簡單, 但已經斷層了. 估計是不合時宜吧.

工作中也就在網路層框架會看見部分C的影子. 自己用C開發久了, 發現C一個弊端是 當一個項目超過 2千行 x 10 時候用C協作

非常難受. C風格是個自由的英雄主義表現.

但是 真實的生活如dota, 我們不是 hero 而只是 那個小兵, 時來運轉會成為超級兵. 哈哈.

但這不重要, 喜歡就好.

  生活不止眼前的苟且 ... ...

   好那我們開始,看看那些關於C基礎的活化石. 真想問 <<C程式設計>> 這門課你真的學好了嗎?

 

正文 - 有點難

1.  int i = 0; ++i 一直繼續會怎樣?

我們先看這樣的測試代碼

#include <stdio.h>
#include <stdlib.h>

/*
 * 測試 int 的最大值
 */
int main(void) {
    int id = 0x7fffffff;

    printf("-1 = %x\n", -1);
    printf("id = %d\n", id);
    ++id;
    printf("id = %d\n", id);
    id += 0x7fffffff;
    printf("id = %d\n", id);
    id += 0x7fffffff;
    printf("id = %d\n", id);

    system("pause");
    return 0;
}

你能算明白測試結果嗎, 如果可以說明你電腦組成原理學的很好. 運行截圖如下

因而 我們得到 int i = 0; ++i 一直繼續的 會是 0->INT_MAX->INT_MIN->0 這樣迴圈的.  例如 skynet 存在這個使用錯誤

        int id = __sync_add_and_fetch(&(ss->alloc_id), 1);
        if (id < 0) {
            id = __sync_and_and_fetch(&(ss->alloc_id), 0x7fffffff);
        }

原作者希望 再從 0開始 , 但卻忘了

#define INT_MIN     (-2147483647 - 1) // minimum (signed) int value
#define INT_MAX       2147483647    // maximum (signed) int value

對於 signed  MAX + MIN = -1 , 因為電腦中 正數從0開始, 負數從-1開始.

 

2. 添加雙引號 的巨集用法

看下麵代碼

// 添加雙引號的巨集
#ifdef _API_MEM
#   define  STRINIFY_(S)    #S
#   define  STRINIFY(S)     STRINIFY_(S)
#   include STRINIFY(_API_MEM)
#   undef   STRINIFY
#   undef   STRINIFY_
#endif

有些工程中使用上面代碼, 來動態的導入頭文件. 核心在於 STRINIFY_ 和 STRINIFY 兩個巨集使用. 我們測試一下

#include <stdio.h>
#include <stdlib.h>

#   define  STRINIFY_(S)    #S
#   define  STRINIFY(S)     STRINIFY_(S)

#define _API_MEM api.h

// 測試添加雙引號巨集
int main(void) {

    puts(STRINIFY_(_API_MEM));
    puts(STRINIFY(_API_MEM));

    system("pause");
    return 0;
}

運行結果是

通過這個發現, 如果直接用 STRINIFY_ 不會將參數展開了. 這也是一個C行業淫蕩的技巧了. 但是覺得大巧若拙 

個人覺得 最好做法是

#define _API_MEM "api.h"
#ifdef _API_MEM # include _API_MEM #endif

 

3. 除了sizeof, 其實還有 offsetof

直接看例子

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>

#define UDP_ADDRESS_SIZE 19 // ipv6 128bit + port 16 bit + 1 byte type

struct write_buffer {
    struct write_buffer* next;
    void* buffer;
    char* ptr;
    int sz;
    bool userobject;
    uint8_t udp_address[UDP_ADDRESS_SIZE];
};

/*
 * 測試 巨集 offsetof
 */
int main(int argc, char* argv[]) {

    printf("offsetof(struct write_buffer, udp_address[0]) = %d\n", offsetof(struct write_buffer, udp_address[0]));
    printf("offsetof(struct write_buffer, udp_address) = %d\n", offsetof(struct write_buffer, udp_address));

    system("pause");
    return 0;
}

運行的結果如下

通過上面 可以知道 offsetof 其實計算的是結構體中欄位的偏移量. 關於結構體的記憶體計算基礎能力, 必須要掌握的. 洞悉記憶體結構很重要.

其實 offsetof 是 stddef.h 中定義的一個 巨集 如下

#define offsetof(s,m) ((size_t)&(((s*)0)->m))

是不是很清爽. 就是這樣, 沒事簡單的.

其實上面代碼還隱含一個 關於 數組的 細節 . int a[10];  &a[0] == a == &a 地址是相同的.

 

4. 如何構造一個只能在堆上分配結構體?

//堆上 聲明結構體
struct request_open {
    int id;
    int port;
    uintptr_t opaque;
    char host[];
};

就是上面那樣, 加了[], 表示不完全類型. 只能在堆上分配記憶體. 使用方法.

struct request_open *open = malloc(sizeof(struct request_open) + sizeof(char) * 19);

這種結構一般在底層庫會看見. 一些老的程式員喜歡這麼寫

//堆上 聲明結構體
struct request_open {
    int id;
    int port;
    uintptr_t opaque;
    char host[0];
};

//堆上 聲明結構體
struct request_open {
    int id;
    int port;
    uintptr_t opaque;
    char host[1];
};

因為老的編譯器不支持 char host[]; 後面標準加了. 後來沒改過習慣.

 

5. 如何構造一個在棧上初始化的指針變數

說的不好明白, 或者這麼問, 下麵定義的類型怎麼解.

struct cstring_data {
    char* cstr;                                 //保存字元串的內容
    uint32_t hash;                                //字元串hash,如果是棧上的保存大小
    uint16_t type;                                //主要看 _INT_STRING_* 巨集,預設0表示臨時串
    uint16_t ref;                                //引用的個數, 在 type == 0時候才有用
};

typedef struct _cstring_buffer {
    struct cstring_data* str;
} cstring_buffer[1];                            //這個cstring_buffer是一個在棧上分配的的指針類型

上面也是底層庫中會遇到一個技巧.

當聲明cstring_buffer cb; 後.可以直接cb->str調用它,

當 cb 傳入到 函數中. 仍然可以 cb->str. 可以理解為這個值是棧上的但是可以當指針變數用法去使用. 看下麵也許好理解

typedef struct _jmp_buf { 
    int _jb[_JBLEN + 1]; 
} jmp_buf[1];

這個是 setjmp.h 里的一行定義,把一個 struct 定義成一個數組。

這樣,在聲明 jmp_buf 的時候,可以把數據分配到堆棧上。但是作為參數傳遞的時候則作為一個指針. 

擴展一下閱讀理解可以看下麵. 應該可以知道為什麼這麼搞.

//特殊的數組 聲明結構體
#define _INT_STRING_ONSTACK        (4)                //標識 字元串分配在棧上
                                                //0 潛在 標識,這個字元串可以被回收,游離態

#define _INT_ONSTACK            (128)            //棧上記憶體大小

struct cstring_data {
    char* cstr;                                 //保存字元串的內容
    uint32_t hash;                                //字元串hash,如果是棧上的保存大小
    uint16_t type;                                //主要看 _INT_STRING_* 巨集,預設0表示臨時串
    uint16_t ref;                                //引用的個數, 在 type == 0時候才有用
};

typedef struct _cstring_buffer {
    struct cstring_data* str;
} cstring_buffer[1];                            //這個cstring_buffer是一個在棧上分配的的指針類型

/*
 * v : 是一個變數名
 *
 * 構建一個 分配在棧上的字元串.
 * 對於 cstring_buffer 臨時串,都需要用這個 巨集聲明創建聲明,
 * 之後可以用 CSTRING_CLOSE 關閉和銷毀這個變數,防止這個變數變成臨時串
 */
#define CSTRING_BUFFER(v) \
    char v##_cstring[_INT_ONSTACK] = { '\0' }; \
    struct cstring_data v##_cstring_data = { v##_cstring, 0, _INT_STRING_ONSTACK, 0 }; \
    cstring_buffer v; \
    v->str = &v##_cstring_data;

 

6. 那些年總有個align欄位進行記憶體對齊

/*位元組對齊的類型Align,為了優化CPU讀取*/
typedef union {
    long        l_dummy;
    double      d_dummy;
    void        *p_dummy;
} Align;

/*標誌大小,預設是4位元組*/
#define MARK_SIZE       (4)
/*記憶體塊頭結點,雙向鏈表結點size,filename,line都是為了調試添加的調試信息.prev和next是雙向鏈表的核心*/
typedef struct {
    int         size;
    char        *filename;
    int         line;
    Header      *prev;
    Header      *next;
    unsigned char       mark[MARK_SIZE];
} HeaderStruct;

/*Align類型的位元組大小*/
#define ALIGN_SIZE      (sizeof(Align))
/*這是個不錯的技巧,求最小的n使得n*ALIGN_SIZE>=val成立,n,val,ALIGN_SIZE都屬於自然數*/
#define revalue_up_align(val)   ((val) ? (((val) - 1) / ALIGN_SIZE + 1) : 0)
/*將HeaderStruct按照Align劃分,找到最小的n,使得n*ALIGN_SIZE>=sizeof(HeaderStruct),在自然數集中*/
#define HEADER_ALIGN_SIZE       (revalue_up_align(sizeof(HeaderStruct)))

/*實現了memory.h介面中Header不完全類型,Align是對齊用的,記憶體結構的頭結點.鏈錶鏈接的主要結點*/
union Header_tag {
    HeaderStruct        s;
    Align               u[HEADER_ALIGN_SIZE];
};

 主要看 union Headr_tag 中 Align結構. 保證不同機器上記憶體是對齊的. 比較古老了. 特別底層的庫會見到.

 

7.  可變參數巨集, 那些事

同樣直接看下麵工程中用的示例

//4.0 控制台列印錯誤信息, fmt必須是雙引號括起來的巨集
#ifndef CERR
#define CERR(fmt, ...) \
    fprintf(stderr,"[%s:%s:%d][error %d:%s]" fmt "\r\n",\
         __FILE__, __func__, __LINE__, errno, strerror(errno),##__VA_ARGS__)
#endif/* !CERR */

//4.1 控制台列印錯誤信息並退出, t同樣fmt必須是 ""括起來的字元串常量
#ifndef CERR_EXIT
#define CERR_EXIT(fmt,...) \
    CERR(fmt,##__VA_ARGS__),exit(EXIT_FAILURE)
#endif/* !ERR */

#ifndef IF_CERR
/*
 *4.2 if 的 代碼檢測
 *
 * 舉例:
 *        IF_CERR(fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP), "socket create error!");
 * 遇到問題列印日誌直接退出,可以認為是一種簡單模板
 *    code : 要檢測的代碼 
 *  fmt     : 必須是""括起來的字元串巨集
 *    ...     : 後面的參數,參照printf
 */
#define IF_CERR(code, fmt, ...)    \
    if((code) < 0) \
        CERR_EXIT(fmt, ##__VA_ARGS__)
#endif //!IF_CERR

#ifndef IF_CHECK
/*
 * 是上面IF_CERR 的簡化版很好用
 */
#define IF_CHECK(code) \
    if((code) < 0) \
        CERR_EXIT(#code)
#endif // !IF_CHECK

那 傳說中的 3顆痣, 就是可變參數巨集的一切o(∩_∩)o

 

8. 簡單的謝幕. 還是巨集

一個數如何和0比較,真的是 == 嗎. 其實好的思路是定義閥值.

//3.0 浮點數據判斷巨集幫助, __開頭表示不希望你使用的巨集
#define __DIFF(x, y)                ((x)-(y))                      //兩個表達式做差巨集
#define __IF_X(x, z)                ((x)<z&&(x)>-z)                //判斷巨集,z必須是巨集常量
#define EQ(x, y, c)                 EQ_ZERO(__DIFF(x,y), c)        //判斷x和y是否在誤差範圍內相等

//3.1 float判斷定義的巨集
#define _FLOAT_ZERO               (0.000001f)                      //float 0的誤差判斷值
#define EQ_FLOAT_ZERO(x)          __IF_X(x,_FLOAT_ZERO)            //float 判斷x是否為零是返回true
#define EQ_FLOAT(x, y)            EQ(x, y, _FLOAT_ZERO)            //判斷表達式x與y是否相等

謝幕吧 : [

  老師佈置一個作業, 問學生, 看見那個晾衣桿嗎. 誰能幫我測試出高度來.

一個同學自告奮勇的把晾衣桿放倒了. 測試出長度 為 1.5m.

  老師把他罵了一頓, 我要的是高度, 不是長度.

]

//5.0 獲取數組長度,只能是數組類型或""字元串常量,後者包含'\0'
#ifndef LEN
#define LEN(arr) \
    (sizeof(arr)/sizeof(*(arr)))
#endif/* !ARRLEN */

 

後記 - 認真做容易的

  錯誤是難免的, 歡迎吐槽交流.  ... 還有下半輩子的苟且.  哈哈, 但是在變化, 那會變得有意思, O(∩_∩)O哈哈~

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 問題描述: Given a positive integer n, break it into the sum of at least two positive integers and maximize the product of those integers. Return the maxim ...
  • 一丶可命名元組(nametuple) ...
  • 1.include語句 使用include語句可以告訴PHP提取特定的文件,並載入它的全部內容 1 <?php 2 inlude "fileinfo.php"; 3 4 //此處添加其他代碼 5 ?> 使用include語句可以告訴PHP提取特定的文件,並載入它的全部內容 1 <?php 2 inl ...
  • 命名空間其實只是一個形式,最終目的是重構代碼,但這個過程想要一蹴而就是不可能的。 一開始給了一個偽命題:基於ThinkPHP的重構(不要為什麼)。經過一段的實踐,發現這是一個大錯特錯的思維方式,其中遇到的坑在此略過不表。 首先,不要想著全盤基於命名空間重寫,而應該是基於局部的。 最終思考後的結果,是 ...
  • 在上一篇《java事務(二)——本地事務》中已經提到了事務的類型,並對本地事務做了說明。而分散式事務是跨越多個數據源來對數據來進行訪問和更新,在JAVA中是使用JTA(Java Transaction API)來實現分散式的事務管理的。但是在本篇中並不會說明如何使用JTA,而是在不依賴其他框架以及j ...
  • HTTP協議(HyperText Transfer Protocol,超文本傳輸協議)是網際網路上應用最為廣泛的一種網路傳輸協議,所有的WWW文件都必須遵守這個標準。 HTTP是一個基於TCP/IP通信協議來傳遞數據(HTML 文件, 圖片文件, 查詢結果等)。 HTTP協議工作於客戶端-服務端架構為 ...
  • atitit.詞法分析原理 詞法分析器 (Lexer) 1. 詞法分析(英語:lexical analysis)1 2. ;實現詞法分析程式的常用途徑:自動生成,手工生成.[1] 2 2.1. 詞法分析程式的功能2 2.2. 如何描述詞素3 2.3. 單詞token3 2.4. Token的類型,根 ...
  • 註:以下文章原文來自於Dr Charles Severance 的 《Python for Informatics》 本書中的許多例子關註的是讀取文件並查找數據,但在互聯網中還有許多不同信息源。 本章我們將偽裝成瀏覽器用超文本傳送協議(HTTP)從網站獲取網頁,通讀並分析它。 12.1 超文本傳送協 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...