• C语言学习笔记


    更新:因为开始写PA2了,因此决定重回这个坑,顺便记录一下解决一些C问题的方法,不一定和语法相关


    现在才知道 oi 用的是 C-with-STL... 感觉自己对这个看起来很单纯的语言仍然不够了解

    打算仔仔细细地写一写自己不太会的东西,包括与 C++ 不同的语法、宏(macro)的技巧、指针的技巧等等

    语法

    enum

    可以利用enum的特点来引入最大下标开数组

    enum {PC,R1,R2,...,R7,R_SIZE};
    
    

    那么这里的R_SIZE天然就是最大下标

    数组初始化

    简单的就不说了。通常会有这样的情形:我们需要一个lookup-table,并且希望这是const的

    dom的范围很大,但是实际上有用的lookup项很少(比如说为每个二元运算分配一个优先级)
    那么就可以这么写

    enum TOKENS {
    	// blah blah blah....
    } ;
    
    // priority for _binary operators_
    static const int priority[512] = {
      [TK_OR] = 4,
      
      [TK_AND] = 5,
      
      [TK_NEQ] = 9,
      [TK_EQ] = 9,
      
      [TK_LEQ] = 10,
      [TK_GEQ] = 10,
      [TK_LT] = 10,
      [TK_GT] = 10,
      
      ['+'] = 12,
      ['-'] = 12,
      
      ['*'] = 13,
      ['/'] = 13,
      ['%'] = 13,
    };
    

    struct

    struct里面不能有函数(好严格..)

    C语言中的结构体有两种定义方式

    struct MyStruct {
          int tmp;
    } ;
    
    struct MyStruct a;
    

    或者可以

    typedef struct tmpMyStruct {
        tmpMyStruct *p;
        int tmp;
    } MyStruct;
    
    MyStruct a;
    

    这两者的区别在于下面的方法把struct定义为一个类型,并且引入了一个中间名字tmpStruct,这使得我们可以在结构体中定义结构体变量(考虑一个指针节点的组成)

    malloc/free

    C++用的更多的是newdelete,C里面用的是mallocfree
    假设有这么一个

    typedef struct tmpNode {
          struct tmpNode *next;
          int value;
    } Node;
    
    Node *head;
    

    我们要给head指针分配内存空间,用的就是

    head = (Node*) malloc(sizeof(Node));
    

    这个可以类比给Nodenew了一个对象,不带初始化函数
    这里分配的内存不包括结构体内部指针指向的地址的内存,这个还需要在自己写的初始化函数里再malloc一次

    一个我常用的小技巧是把初始化函数自己实现一遍,返回一个对象的指针

    类似的,在free的时候需要自己实现对象的析构:比如说free一棵红黑树,比如说free一个链表。通常递归free掉所有节点就够了

    const 数组下标

    C语言中const修饰的int变量不能定义数组大小,因为这是一个只读变量而不是常量,通常我们会用#define

    bool

    C里面的bool是在C99以后才有的,如果要用true和false需要引用stdbool.h,或者自己define

    逗号表达式

    逗号的优先级最低,一段逗号表达式按顺序从左到右求值,一整段逗号表达式的值由最右边的式子决定
    比如说(a = 3, a *= 2)的值就是6

    static静态变量

    分为全局静态变量和局部静态变量。静态变量放在.bss.data字段中

    全局静态变量在链接的时候不能被其他.o文件引用,可以看成是一种private封装
    局部静态变量不在栈上,作用域和局部变量一致,生命周期却和全局变量一致

    volatile关键字

    这个是看CSAPP学到的姿势

    对于多线程共同访问一个全局变量的情况,可以使用volatile来告诉编译器对于该变量只从内存中取值

    这可以防止编译器过度优化使得无锁并行的程序出错

    macro

    未定义macro的初值

    考虑如下代码

    #include <stdio.h>
    int main(void) {
    	#if aa==bb
    		puts("YES");
    	#else
    		puts("NO");
    	#endif
    	return 0;
    }
    

    输出是YES
    原因在于未定义的macro默认值都是0

    同时 #if 后跟着的表达式必须是常亮表达式。很好理解,因为是编译期行为

    #ifndef

    在多文件编程的时候我们会include若干.h文件。.h文件的include原理就是复制粘贴,因此如果多次include会出现奇奇怪怪的错
    所以我们需要在.h前后加上

    #ifndef _SOMETHINGSPECIAL_
    #define _SOMETHINGSPECIAL_
    // do something ...
    #endif
    

    其中_SOMETHINGSPECIAL_是不同头文件不同的一个变量名,这个东西的意思是如果没有定义过这个宏就执行内部内容,否则就跳过。而内部代码定义了这样一个宏,就保证了只会被执行一次(即使被include多次)

    stringify和concatenation

    假设要实现floatint的最大值函数

    #define CONCAT_TMP(X, Y) X ## Y
    #define CONCAT(X, Y) CONCAT_TMP(X, Y)
    
    #define DEF_MAX 
    	int		CONCAT(max_, int)(int x,  int y) { return x > y ? x : y;} 
    	float	CONCAT(max_, float)(int x,int y) { return x > y ? x : y;}
    

    多行macro&&任意参数

    很多时候一行宏定义不够用,于是我们就可以通过在行末加''符号定义多行macro
    如果出现了参数不明确但是操作一致的情况,我们还可以在定义的时候采用...作为形参,使用的时候用__VA_ARGS__这个宏

    #define IS_DEBUG true
    #define DEBUG(...) {
    if (IS_DEBUG) 
    printf(__VA_ARGS__);
    }
    
    

    while(0)

    一个很怪的用法,大概可以写成下面这样:

    #define CHECK(EXP) do {
      if (EXP) printf("WARNING! " #EXP " CHECK FAILED!
    "); } while (0)
    

    这个宏函数会检查一个表达式EXP,但是这里把主体语句用一个do while(0)括起来了
    好处大概有这么几个:

    1. 可以包裹多条语句,在展开之后可以保证这些语句在一个大括号内,这样可以保证操作对于if then else的整体性
    2. (新增)这样做的话宏的内部相对独立,就可以随便开临时变量了

    X-macro

    这个用于解决相关联的表项数据分布于不同文件时,如何方便修改的问题

    一个比较烂的的例子是抄来的:

    enum color {RED,GREEN,BLUE};
    char *str[] = {"RED","GREEN","BLUE"};
    

    实际代码中可能不止两处,可能相隔很远

    现在如果要在红色和绿色之间加入黄色,那么所有硬编码的地方都需要修改

    所谓 X macro就是这样一类解决多处硬编码的修改问题

    #define COLOR(X) 
    	X("RED",RED) 
    	X("BLUE",BLUE) 
    	X("GREEN",GREEN)
    
    #define X(a,b) b,
    	enum color {COLOR(X)};
    #undef X
    
    #define X(a,b) a,
    	char *str[] = {COLOR(X)};
    #undef X
    

    怎么说呢,有点像call back function的感觉

    指针

    特殊指针

    NULL指针在C中的定义是(void *)0

    常量指针const int *pint const *p:指向常量的指针(a pointer that points to a const)

    指针常量int *const p:一个自己是常量的指针(a const pointer)

    上面两类还可以复合/套娃....类比就行

    指针变量的阅读

    具体可以看这篇文章

    链接

    符号表

    这里的符号表和编译原理的符号表又不太一样

    编译的时候可以多个编译单元编译成.o文件,最后合并成一个可执行文件(elf),这个合并的过程就是链接

    在链接的时候,需要维护一些跨编译单元的1. 函数调用2. 全局变量引用,符号表就是用来给链接器提供这个信息的

    根据这个角度,就可以知道为什么局部变量和宏不会出现在符号表中了,因为它们在链接时1. 不会被引用和修改 2. 已经被展开了

    staticinline

    inline表示建议编译器内联这段函数,但仅含有inline的函数定义不是一个函数声明,因此不会出现在符号表中

    在GCC开启-O0级别优化的时候,就会出错

    所以有两种方法解决:

    1. inline定义后加一个函数声明
    2. inline关键字前加static关键字

    而对于仅含有static的函数,如果它未被调用,则开启-Wall-Werror的时候就会报Function defined but not used错,这是因为static函数只可能被当前.c文件调用,而查手册可以发现-Wunused-function恰好会指出这种情况。解决的方法可以是用static inline代替static,或者删掉这个函数定义和声明

    杂项

    编译器选项

    感觉-Wall那个应该放这里的

    64位下,对于开启-O1及以上优化级别的程序,截止当前版本的GCC默认会省略%rbp寄存器的保存(压栈、记录栈顶),这样既可以变快,又可以多一个通用寄存器。在做ICS Labs的时候要写一个自己的setjmp()longjmp(),然后我就卡在了获得ret地址这一步,原因就是这个东西破坏了调用规定,导致栈的行为不确定了....

    解决方法很简单,直接自己写一整个的函数,而不是在函数内部内联汇编,这样callee就是自己维护的了,想怎么做就怎么做

    本文来自博客园,作者:jjppp。本博客所有文章除特别声明外,均采用CC BY-SA 4.0 协议

  • 相关阅读:
    单 GPU 程序 转 多 GPU 程序
    Ubuntu 开启ssh 连接,设置静态 ip 地址。
    Ubuntu 添加新硬盘 之 分区+格式化+挂载
    夜间模式的开启与关闭,父模板的制作
    开始Flask项目
    完成登录与注册页面的前端
    JavaScript 基础,登录前端验证
    CSS实例:图片导航块
    导航,头部,CSS基础
    web基础,用html元素制作web页面
  • 原文地址:https://www.cnblogs.com/jjppp/p/14195067.html
Copyright © 2020-2023  润新知