概述
修饰词
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 = 2,M(a, b)所得结果不是4,应该是3
因为#define只是起到替换作用,所以最后的表达式应该替换为 1+1*2,所以结果为3
因此形参都最好加上括号:
#define M(a,b) ((a)*(b))
这样如果 a = 1+1,b = 2,M(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语言回顾(修饰词篇)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复