• C语言回顾(修饰词篇)


    修饰词

    const

    const修饰的量为一个常量即不能被修改的量。

    1、const用于定义常变量时,要进行初始化
        const int a=10; //合法
        const int a;  //非法,未初始化,虽然不会出错,但是后面也不能对其赋值
    2、数据类型对于const而言是透明的(也就是说确定数据类型的时候不必带上它)
        const int a=10;   等价于 int const a=10;
        const int *p1=&a; 等价于 int const *p1=&a; (两者都是修饰*p1)  但不等价于 int *const     p1=&a; (这个是修饰p1) 
    3、const用于封锁直接修饰的内容,该内容变为只读,该变量不能作为左值(左值:放在赋值号‘=’的左边,使用变量的写权限)
        const int a=10;//const封锁a
        a=100; //a作为左值,使用a的写权限,非法
        int b=a; //使用a的读权限,合法
    
        const int *p1=&a; //const修饰*p1,将p1作为左值合法,将*p1作为左值非法
        p1=&b; //使用p1做左值,合法
        *p1=200;//使用*p1做左值,非法
        int * const p2=&a; //const修饰p2,将p2作为左值非法,将*p2作为左值合法
        p2=&b;//使用p2做左值,非法
        *p2=100;//使用*p2做左值,合法
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    volatile

    volatile的本意是“易变的” 因为访问寄存器要比访问内存单元快的多,所以编译器一般都会作减少存取内存的优化,但有可能会读脏数据。当要求使用volatile声明变量值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。

    1、告诉编译器不优化

    比如要往某一地址送两指令:
    int *ip =...; //设备地址
    *ip = 1; //第一个指令
    *ip = 2; //第二个指令
    以上程序compiler可能做优化而成:
    int *ip = ...;
    *ip = 2;
    结果第一个指令丢失。如果用volatile, compiler就不允许做任何的优化,从而保证程序的原意:
    volatile int *ip = ...;
    *ip = 1;
    *ip = 2;
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    2、用volatile定义的变量会在程序外被改变,每次都必须从内存中读取,而不能重复使用放在cache或寄存器中的备份。

    应用场景:

    1、中断服务程序中修改的供其它程序检测的变量需要加volatile。变量所在内存的值可能已经被改了,但是在其他程序中的调用可能一直读取的是寄存器中的值,从而导致程序运行错误。(内存是SRAM,寄存器是R1,R2等或者cache)

    2、多任务环境下各任务间共享的标志应该加volatile

    频繁地使用volatile很可能会增加代码尺寸和降低性能,因此要合理的使用volatile。

    __attribute__

    __attribute__用来设置函数的属性(Function Attribute )、变量的属性(Variable Attribute )和类型的属性(Type Attribute

    __attribute__ 语法格式为:__attribute__ ((attribute-list))

    其位置约束:放于声明的尾部“;”之前。说白了,它就是相当于是个形容词,它存在于被形容的函数或变量或类型的后面,紧挨着,进行说明。

    GNU 和ARM编译器都支持__attribute__

    aligned (alignment)

    指定对象的对齐格式(以字节为单位),属于类型的属性,多用来形容结构体类型,注意,是类型,不是变量。变量属性也有 aligned,不常用,变量属性的话就跟在变量后面。

    struct S { 
    short b[3]; 
    } __attribute__ ((aligned (8)));
    /*定义了一个结构体类型,并且指定其对齐方式为8字节对齐 */
    
    struct m
    {
        char a;
        int b;
        short c;
    }__attribute__((aligned(4))) mm;
    /*定义了一个结构体类型,并指定对齐方式为4字节对齐,因为其属于类型属性,形容的是类型,所以书写位置在定义的结构体类型的后面,而不是结构体变量的后面*/
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    packed

    就是告诉编译器取消字节对齐(使用1字节对齐),按照实际占用字节数进行对齐,属于类型的属性,多用来形容结构体,联合体。

    struct packed_struct
    {
         char c;
         int  i;
         struct unpacked_struct s;
    }__attribute__ ((__packed__));
    /*定义了结构体类型,并取消了字节对齐*/
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    used

    __attribute__((used))可以是函数的属性也可以是变量的属性,修饰函数就跟在函数后面,修饰变量就跟在变量后面。其作用是告诉编译器,我定义的东西,就算没有调用,也不能被优化掉

    unused

    __attribute__((unused))可以是函数的属性也可以是变量的属性,修饰函数就跟在函数后面,修饰变量就跟在变量后面。表示该函数或变量可能不使用,这个属性可以避免编译器产生警告信息

    weak

    __attribute__((weak))可以是函数的属性也可以是变量的属性,修饰函数就跟在函数后面,修饰变量就跟在变量后面。其所形容的函数是个弱符号。

    在c语言中,函数和初始化的全局变量是强符号,未初始化的全局变量是弱符号。强符号和弱符号的定义是连接器用来处理多重定义符号的,它的规则是:不允许多个强符号;如果一个强符号和一个弱符号,这选择强符号;如果多个弱符号,则任意选一个。

    通俗的说:当代码中有两个相同的函数或者变量。其中一个以__attribute__((weak))修饰了,那么这个就是弱符号。这时候编译器在编译的时候即使这两个名字是一样的也不会报错。当程序中调用的时候会调用没有被修饰那个,修饰的那个将会被忽略掉。

    使用场景:

    /* 不确定其他模块是否定义了这个函数,那么如果直接调用可能会其他模块未定义而出错。
    可以自己定义一个,如果外部定义了,则调用外部的,如果外部没有定义,则使用自己的*/
    int  __attribute__((weak))  func(......)
     
    {
     
        return 0;
     
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    at

    __attribute__((at))绝对定位,可以把变量或函数绝对定位到Flash中,或者定位到RAM。

    /*对于变量,在其后边加修饰;而对于函数,在声明处加修饰。*/
    int value __attribute__((at(0x20000000))) = 0x33;//将变量定位到RAM中
    const char ziku[] __attribute__((at(0x0800F000)))   = {0x1, 0x2, 0x3}; //将只读的常量放在flash中
    void func (void) __attribute__((at(0x08001000)));//将函数放在指定flash中
     
    void func (void) {
        int i;
        for (i = 0; i < 100; i++){
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    变量指定的地址只能位于RAM区;常量和代码只能位于Flash区。不然在链接阶段会出错

    section

    __attribute__((section(“section_name”)))函数或数据放入指定名为"section_name"对应的段中。

    const int descriptor[3] __attribute__ ((section ("descr"))) = { 1,2,3 };
    long long rw[10] __attribute__ ((section ("RW")));
    /*编译后会生成段 descr,descriptor会放在该段所在的地址*/
    
    • 1
    • 2
    • 3

    注意:变量的段定义在RAM中,常量和函数的段定义在Flash中。变量和常量不能放在一个段中,会报错。自定义的变量段在内存中的分布是连续的,且根据名字进行排列。

    宏定义的用法

    宏定义 #define 在C程序编译的第一个步骤预处理阶段被编译,其作用就是将宏名替换为替换列表中得内容。

    宏定义的一般写法

    #define   标识符(也称为宏名)   替换列表
    (替换列表可以是数,字符串字面量,标点符号,运算符,标识符,关键字,字符常量。注意:替换列表是可以为空的)
    
    • 1
    • 2

    无参定义

    //定义常量
    #define N 100
    //重定义数据类型
    #define pin (int*)
    #define u32 unsigned int
    //定义一个循环
    #define LOOP for(;;)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    有参定义

    #define 宏名(形参表) 替换列表

    需要注意的是“边缘效应”

    例如:
    #define M(a,b) a*b
    假设 a = 1, b = 2, M(a, b)所得结果为 2,这应该都没问题
    但如果 a = 1+1,b = 2M(a, b)所得结果不是4,应该是3
    因为#define只是起到替换作用,所以最后的表达式应该替换为 1+1*2,所以结果为3
    
    因此形参都最好加上括号:
    #define M(a,b) ((a)*(b))
    这样如果 a = 1+1,b = 2M(a, b)所得结果不是4,因为替换后为
    (( 1+1*2 ))
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    多行定义

    在进行宏定义的时候可以使用反斜杠接续符 ’ \ ’ ,来接续上一行,反斜杠作为接续符时,在本行其后面不能再有任何字符,空格都不行

    #define MAX(X,Y) do { \
    语句1; \
    语句2; \
    } while(0) 
    
    • 1
    • 2
    • 3
    • 4

    条件编译

    在大规模的开发过程中,特别是跨平台和系统的软件里,define最重要的功能是条件编译。

    #ifndef __headerfileXXX__
    #define __headerfileXXX__
      …
      …
     #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    #ifdef XXX#else#endif
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    取消宏定义

    #define 的作用域为自 #define 那一行起到源程序结束。如果要终止其作用域可以使用 #undef 命令

    #undef  标识符
    
    • 1

    #、##、@#

    "#"用来把参数转换成字符串,是给参数加上双引号。
    "##"则用来连接前后两个参数,把它们变成一个字符串,
    "#@"是给参数加上单引号。

    #define CAT(x,y) x##y       /* CAT(1,"abc") => "1abc" */
    #define TOCHAR(a) #@a       /* TOCHAR(1) => '1' */
    #define TOSTRING(x) #x      /* TOSTRING(1) => "1" */
    
    • 1
    • 2
    • 3
    #define f(a,b) a##b 
    #define d(a) #a 
    #define s(a) d(a) 
    
    void main( void ) 
    { 
        puts(d(f(a,b))); 
        puts(s(f(a,b))); 
    } 
    
    输出结果: 
    f(a,b) 
    ab
        
    /*这就涉及到了嵌套的时候先展开还是先不展开,简单说
    #define d(a) #a   以"#"开头的,直接替换,不展开
    #define s(a) d(a) 非以"#"开头的,先展开,再替换 */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    预定义宏

    描述
    __FILE__当前源文件的名称,字符串常量
    __DATE__当前源文件编译日期用 “mm dd yyy”形式的字符串常量表示
    __LINE__当前源义件中的行号,用十进制整数常量表示
    __TIME__当前源文件的最新编译吋间,用“hh:mm:ss”形式的字符串常量表示
    __FUNCTION__当前调用的函数名 字符串
    __STDC__如果今前编泽器符合ISO标准,那么该宏的值为1,否则未定义
    __STDC_VERSION__如果当前编译器符合C89,那么它被定义为199409L;如果符合C99,那么它被定义为199901L:在其他情况下,该宏为宋定义

    其他的技巧

    #define test ("1" "2" "3") //test的值为"123"
    
    • 1
  • 相关阅读:
    常量指针、指针常量,指针数组、数组指针,函数指针、指针函数
    轻松掌握JavaScript字符串操作的10个小技巧
    async await
    玉米地里的小鸟
    WebRTC系列-H.264预估码率计算
    FPGA CFGBVS 管脚接法
    mybatis中mapper.xml热加载
    Mac M2/M3 芯片环境配置以及常用软件安装-前端
    DGIOT实战教程-监控摄像头接入(v4.6.0)
    2022牛客暑期多校训练营2 个人题解
  • 原文地址:https://blog.csdn.net/g360250466/article/details/125991842