I、 O 、__IO是什么意思?

这是ST库里面的宏定义,定义如下:

define I volatile const /!< defines ‘read only’ permissions /
#define
O volatile /!< defines ‘write only’ permissions /
#define __IO volatile /!< defines ‘read / write’ permissions /

显然,这三个宏定义都是用来替换成 volatile 和 const 的,所以我们先要了解 这两个关键字的作用:

volatile
简单的说,就是不让编译器进行优化,即每次读取或者修改值的时候,都必须重新从内存或者寄存器中读取或者修改。

一般说来,volatile用在如下的几个地方:
1、中断服务程序中修改的供其它程序检测的变量需要加volatile;
2、多任务环境下各任务间共享的标志应该加volatile;
3、存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义;

1)一个参数既可以是const还可以是volatile吗?解释为什么。
2); 一个指针可以是volatile 吗?解释为什么。
3); 下面的函数有什么错误:
int square(volatile int ptr)
{
return
ptr ptr;
}

1)是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。
2); 是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个buffer的指针时。
3) 这段代码有点变态。这段代码的目的是用来返指针ptr指向值的平方,但是,由于ptr指向一个volatile型参数,编译器将产生类似下面的代码:
int square(volatile int ptr)
{
int a,b;
a =
ptr;
b = ptr;
return a
b;
}
由于ptr的值可能被意想不到地该变,因此a和b可能是不同的。结果,这段代码可能返不是你所期望的平方值!正确的代码如下:
long square(volatile int
ptr)
{
int a;
a = ptr;
return a
a;
}

const
只读变量,即变量保存在只读静态存储区。编译时,如果尝试修改只读变量,则编译器提示出错,就能防止误修改。
const与define
两者都可以用来定义常量,但是const定义时,定义了常量的类型,所以更精确一些(其实const定义的是只读变量,而不是常量)。#define只是简单的文本替换,除了可以定义常量外,还可以用来定义一些简单的函数,有点类似内置函数。const和define定义的常量可以放在头文件里面。(小注:可以多次声明,但只能定义一次)

const与指针
int me;
const int p1=&me; //p1可变,p1不可变 const 修饰的是 p1,即p1不可变
int const p2=&me; //p2不可变,p2可变 const 修饰的是 p2,即p2不可变
const int const p3=&me; //p3不可变,p3也不可变 前一个const 修饰的是 *p3,后一个const 修饰的是p3,两者都不可变

前面介绍了 volatile 和 const 的用法,不知道大家了解了没?了解了后,下面的讲解就更加容易了:
I :输入口。既然是输入,那么寄存器的值就随时会外部修改,那就不能进行优化,每次都要重新从寄存器中读取。也不能写,即只读,不然就不是输入而是输出了。
O :输出口,也不能进行优化,不然你连续两次输出相同值,编译器认为没改变,就忽略了后面那一次输出,假如外部在两次输出中间修改了值,那就影响输出
__IO:输入输出口,同上

为什么加下划线?

原因是:避免命名冲突
一般宏定义都是大写,但因为这里的字母比较少,所以再添加下划线来区分。这样一般都可以避免命名冲突问题,因为很少人这样命名,这样命名的人肯定知道这些是有什么用的。
经常写大工程时,都会发现老是命名冲突,要不是全局变量冲突,要不就是宏定义冲突,所以我们要尽量避免这些问题,不然出问题了都不知道问题在哪里。

attribute含义

而使用attribute((unused))可以告诉编译器忽略此告警:

  1. #include <stdio.h>
  2. int main(void)
  3. {
  4. printf("main\n");
  5. }
  6. __attribute__((unused)) static void a(void)
  7. {
  8. printf("a\n");
  9. }

attribute((section(“”)))属性含义

将t2放置在st2段中,将t3放置在st3段中。
验证这些部分,可以使用下面例子提供的objdump命令,在操作过程中,注意objdump的参数和st1段中三个变量的位置和初始值的保存。
现将该段测试使用的objdump的参数及含义整理如下:
objdump的参数 作用
-h 显示段表
-d 对包含机器指令的段进行反汇编
-s 显示文件的所有内容
—section=NAME 仅显示指定段的信息
对于自定义段的使用方法,可以参见下面的代码,实在没有找到具有针对性质的相关材料,就个人理解对下面的代码进行解释一下。
line 8、line 12、line 13将t11、t12和t13放置在了st1段中,通过后面的objdump的输出可以看出来。line 22引用了外部的针对段st1的开始处的结构体,line 23声明了段st1的结束位置,从这两行的代码可以猜测出,在设置段的区域,“start_”字符串开始并且紧接着是目标段名(如这个示例中的段名称为st1,则st1段开始的变量为startst1)的变量记录了目标段的开始位置,“__stop”字符串开始的变量记录了目标段的结束位置(对于本例,stop_st1指向的地址是st1段后的第一个非本段的地址,即st2段的起始地址),它们之间的差值,就是当前段的长度。
从下面的示例代码中可以发现,此时
attribute修饰符的位置,对最终的目标文件来说,没有影响。
但是,自定义段中是否可以存放其他类型的数据,另外,若可以存放其他类型的数据,这个自定义段的长度是通过什么方法获得的?这两点还需要后续深入研究。但是,就目前情况来看,这两个问题不会困扰后面的代码分析。
1 #include
2
3 struct test {
4 int a;
5 int b;
6 };
7
8 struct test t11
attribute((section(“st1”))) = {
9 .a = 0x00112233,
10 .b = 0xffeeddcc
11 };
12 struct test t12
attribute((section(“st1”)));
13 struct test t13
attribute((section(“st1”))) = {
14 .a = 0x01234567,
15 .b = 0xfedcba98
16 };
17
18 struct test
attribute((section(“st2”))) t2;
19
20
attribute((section(“st3”))) struct test t3;
21
22 extern struct test
start_st1;
23 extern struct test stop_st1;
24
25 int main()
26 {
27 t11.a = 11; t11.b = 12;
28 t2.a = 21; t2.b = 22;
29 t3.a = 31; t3.b = 32;
30 int i = 0;
31 printf(“
startt11: %p\n”, &start_st1);
32 printf(“
stopt11: %p\n”, &stop_st1);
33 printf(“size: %d\n”, abs((long)&
start_st1- (long)&__stop_st1));
34
35 printf(“t1.a:%d, t1.b: %d\n”, t11.a, t11.b);
36 printf(“t2.a:%d, t2.b: %d\n”, t2.a, t2.b);
37 printf(“t3.a:%d, t3.b: %d\n”, t3.a, t3.b);
38 return 0;
39 }
$ gcc test_section.c
$ objdump -h a.out