我是靠谱客的博主 追寻石头,最近开发中收集的这篇文章主要介绍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做左值,合法

volatile

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

1、告诉编译器不优化

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

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字节对齐,因为其属于类型属性,形容的是类型,所以书写位置在定义的结构体类型的后面,而不是结构体变量的后面*/
packed

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

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

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

unused

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

weak

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

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

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

使用场景:

/* 不确定其他模块是否定义了这个函数,那么如果直接调用可能会其他模块未定义而出错。
可以自己定义一个,如果外部定义了,则调用外部的,如果外部没有定义,则使用自己的*/
int  __attribute__((weak))  func(......)
 
{
 
    return 0;
 
}
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++){
    }
}

变量指定的地址只能位于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会放在该段所在的地址*/

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

宏定义的用法

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

宏定义的一般写法

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

无参定义

//定义常量
#define N 100
//重定义数据类型
#define pin (int*)
#define u32 unsigned int
//定义一个循环
#define LOOP for(;;)

有参定义

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

多行定义

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

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

条件编译

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

#ifndef __headerfileXXX__
#define __headerfileXXX__
  …
  …
 #endif
#ifdef XXX#else#endif

取消宏定义

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

#undef  标识符

#、##、@#

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

#define CAT(x,y) x##y       /* CAT(1,"abc") => "1abc" */
#define TOCHAR(a) #@a       /* TOCHAR(1) => '1' */
#define TOSTRING(x) #x      /* TOSTRING(1) => "1" */
#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) 非以"#"开头的,先展开,再替换 */

预定义宏

描述
__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"

最后

以上就是追寻石头为你收集整理的C语言回顾(修饰词篇)的全部内容,希望文章能够帮你解决C语言回顾(修饰词篇)所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(51)

评论列表共有 0 条评论

立即
投稿
返回
顶部