嵌入式C语言自我修养 13:总结

13.1 总结

前面12节的课程,主要针对 Linux 内核中 GNU C 扩展的一些常用 C 语言语法进行了分析。GNU C 的这些扩展语法,主要用来完善 C 语言标准和编译优化。而通过 C 标准的发展过程我们又发现,对于一些编译器扩展的一些特性,或者其它编程语言(如:C++)中的好的特性和语法,C 标准也会适时地吸收进来,作为新的 C 语言标准。

在 GNU C 的这些扩展语法中,attribute 和宏定义是两大特色。在嵌入式底层系统中,尤其是 Linux 内核和 U-boot 中,大量使用 GNU C 扩展的 attribute属性去辅助一些底层机制的实现,或者实现一些编译上的优化。在宏定义方面,通过语句表达式、可变参数宏等特性,我们可以定义一个功能复杂、安全可靠的高质量宏。

本教程所讲的一些特性,都是在实际工作或阅读 Linux 内核驱动源码时经常遇见的一些特性,掌握了这些扩展特性的使用,以后再遇到类似的“奇葩 C 语言”程序,就知道怎么去分析了。除此之外,GNU C 还有一些其它扩展特性,由于他们在内核中用得不是很多,或者说仅仅是做一些编译上的优化,即使不知道也不会影响我们理解代码,限于篇幅关系,所以就暂时不讲了,比如下面这些特性。

  • 属性声明:const
  • 属性声明:constructor、destructor
  • 属性声明:noreturn
  • 属性声明:used、unused
  • 局部标签
  • 嵌套函数
  • ……

大家以后遇到类似的扩展,可以到下面这几个网站上去看看。

  • GNU C语法扩展大全
  • GCC 编译器手册

13.2 C 语言习题测试

下面是几道 C 语言练习题,大家可以做一做。看看学完本教程后,有没有真正的掌握。有什么疑问,可以通过读者圈,或加入QQ群(475504428)与我讨论。

1.下面的程序,在不同编译环境下,比如分别在 C-Free、VC++6.0、TurboC 环境下编译运行,结果是否相等,为什么?

#include<stdio.h>
int main(void)
{
    printf("size: %d\n", sizeof(int);
    return 0;
}

2.定义一个宏,求两个数的最小值。

3.将下面的程序编译为可以在 ARM 平台上运行的可执行文件 a.out,并对其进行反汇编,查看变量 global_val 的地址。

int global_val = 10;
int uninit_val;
int main(void)
{
    int local_val = 20;
    return 0;
}
  1. 在一个工程项目中,有两个源文件如下,分析下面程序的运行结果。

    //func.c
    int a = 10;
    int b;
    int c attribute((weak)) = 30;

    //main.c
    int a;
    int b = 20;
    int c = 40;
    int main(void)
    {
    printf("a: %d\n",a);
    printf("b: %d\n",b);
    printf("c: %d\n",c);
    return 0;
    }

5.定义一个变参函数,实现等级打印控制:ERROR、DEBUG、INFO。用这三个宏分别代表等级打印,比如定义 ERROR 时,只打印错误的信息;定义 DEBUG 时,打印错误和调试信息;定义 INFO 时,所有的打印信息都打印出来。

6.定义一个变参宏,实现等级打印控制:ERROR、DEBUG、INFO。用这三个宏分别代表等级打印,比如定义 ERROR 宏时,只打印错误的信息;定义 DEBUG 时,打印错误和调试信息;定义 INFO 时,所有的打印信息都打印出来。

7.下面是 Linux 内核(Linux4.4.0)中的一些宏定义,请分析它们实现的功能。

#define pr_emerg(fmt, ...)     printk(KERN_EMERG pr_fmt(fmt), ##__VA_ARGS__)
#define pr_alert(fmt, ...)     printk(KERN_ALERT pr_fmt(fmt), ##__VA_ARGS__)
#define pr_crit(fmt, ...)     printk(KERN_CRIT pr_fmt(fmt), ##__VA_ARGS__)
#define pr_err(fmt, ...)     printk(KERN_ERR pr_fmt(fmt), ##__VA_ARGS__)
#define pr_warning(fmt, ...)     printk(KERN_WARNING pr_fmt(fmt), ##__VA_ARGS__)
#define pr_warn pr_warning
#define pr_notice(fmt, ...)     printk(KERN_NOTICE pr_fmt(fmt), ##__VA_ARGS__)
#define pr_info(fmt, ...)     printk(KERN_INFO pr_fmt(fmt), ##__VA_ARGS__)

8.在 Linux 内核启动过程中,启动 log 的最后往往会有这么一行信息。

Freeing unused kernel memory: 468K

请用本课程中的 section 属性声明,分析这段 log 背后的内核初始化及内存释放过程。

9.在嵌入式 Linux 驱动开发中,驱动模块是没有 main() 入口函数的,请用本课程学过的知识分析:驱动是如何运行和初始化的。

10.驱动分析:在 linux4.4 源码 linux-4.4/arch/arm/mach-footbridge/ebsa285.c 中,分析下面代码的含义及 container_of 宏的作用。

MACHINE_START(EBSA285, "EBSA285")
    /* Maintainer: Russell King */
    .atag_offset    = 0x100,
    .video_start    = 0x000a0000,
    .video_end  = 0x000bffff,
    .map_io     = footbridge_map_io,
    .init_early = footbridge_sched_clock,
    .init_irq   = footbridge_init_irq,
    .init_time  = footbridge_timer_init,
    .restart    = footbridge_restart,
MACHINE_END
static void ebsa285_led_set(struct led_classdev *cdev, enum led_brightness b)
{
    struct ebsa285_led *led = container_of(cdev,
            struct ebsa285_led, cdev);

    if (b == LED_OFF)
        hw_led_state |= led->mask;
    else
        hw_led_state &= ~led->mask;
    writeb(hw_led_state, xbus);
}

static enum led_brightness ebsa285_led_get(struct led_classdev *cdev)
{
    struct ebsa285_led *led = container_of(cdev,
            struct ebsa285_led, cdev);

    return hw_led_state & led->mask ? LED_OFF : LED_FULL;
}

13.3 结束语

通过本课程的学习,再加上本节10个习题的练习,相信大家的 C 语言功底肯定又加深了一层!有了这些知识储备基础,基本上就扫除了 Linux 内核的阅读障碍。相信大家在以后的工作、学习中一定会日益精进,不断突破!

最后祝大家工作顺利、学习愉快!

另外,大家如果想系统学习 Linux 内核中的某块知识,或者说有哪些知识掌握得不是很好,想进阶学习,但限于工作、学习繁忙,时间精力有限,无法系统地去学习,也可以跟我联系交流

  • 我的QQ:3284757626)
  • 我的博客:www.zhaixue.cc
  • 我会继续编写相关的的知识和教程,为大家服务。

本教程根据 C语言嵌入式Linux高级编程视频教程 第05期 改编,电子版书籍可加入QQ群:475504428 下载,更多嵌入式视频教程,可关注:
微信公众号:宅学部落(armlinuxfun)
51CTO学院-王利涛老师:http://edu.51cto.com/sd/d344f

原文地址:http://blog.51cto.com/zhaixue/2348625

时间: 2024-08-27 18:30:18

嵌入式C语言自我修养 13:总结的相关文章

嵌入式C语言自我修养 05:零长度数组

5.1 什么是零长度数组 顾名思义,零长度数组就是长度为0的数组. ANSI C 标准规定:定义一个数组时,数组的长度必须是一个常数,即数组的长度在编译的时候是确定的.在ANSI C 中定义一个数组的方法如下: int a[10]; C99 新标准规定:可以定义一个变长数组. int len; int a[len]; 也就是说,数组的长度在编译时是未确定的,在程序运行的时候才确定,甚至可以由用户指定大小.比如,我们可以定义一个数组,然后在程序运行时才指定这个数组的大小,还可以通过输入数据来初始化

嵌入式C语言自我修养 06:U-boot镜像自拷贝分析:section属性

6.1 GNU C 的扩展关键字:attribute GNU C 增加一个 atttribute 关键字用来声明一个函数.变量或类型的特殊属性.声明这个特殊属性有什么用呢?主要用途就是指导编译器在编译程序时进行特定方面的优化或代码检查.比如,我们可以通过使用属性声明指定某个变量的数据边界对齐方式. attribute 的使用非常简单,当我们定义一个函数.变量或类型时,直接在它们名字旁边添加下面的属性声明即可: __atttribute__((ATTRIBUTE)) 这里需要注意的是:attrib

嵌入式C语言自我修养 03:宏构造利器 - 语句表达式

3.1 基础复习:表达式.语句和代码块 表达式 表达式和语句是 C 语言中的基础概念.什么是表达式呢?表达式就是由一系列操作符和操作数构成的式子.操作符可以是 C 语言标准规定的各种算术运算符.逻辑运算符.赋值运算符.比较运算符等.操作数可以是一个常量,也可以是一个变量.表达式也可以没有操作符,单独的一个常量甚至是一个字符串,也是一个表达式.下面的字符序列都是表达式: 2 + 3 2 i = 2 + 3 i = i++ + 3 "wit" 表达式一般用来数据计算或实现某种功能的算法.表

嵌入式C语言自我修养 09:链接过程中的强符号和弱符号

9.1 属性声明:weak GNU C 通过 attribute 声明weak属性,可以将一个强符号转换为弱符号. 使用方法如下. void __attribute__((weak)) func(void); int num __attribte__((weak); 编译器在编译源程序时,无论你是变量名.函数名,在它眼里,都是一个符号而已,用来表征一个地址.编译器会将这些符号集中,存放到一个叫符号表的 section 中. 在一个软件工程项目中,可能有多个源文件,由不同工程师开发.有时候可能会遇

嵌入式C语言自我修养 04:Linux 内核第一宏:container_of

4.1 typeof 关键字 ANSI C 定义了 sizeof 关键字,用来获取一个变量或数据类型在内存中所占的存储字节数.GNU C 扩展了一个关键字 typeof,用来获取一个变量或表达式的类型.这里使用关键字可能不太合适,因为毕竟 typeof 还没有被写入 C 标准,是 GCC 扩展的一个关键字.为了方便,我们就姑且称之为关键字吧. 通过使用 typeof,我们可以获取一个变量或表达式的类型.所以 typeof 的参数有两种形式:表达式或类型. int i ; typeof(i) j

嵌入式C语言自我修养 10:内联函数探究

10.1 属性声明:noinline & always_inline 这一节,接着讲 attribute 属性声明,attribute可以说是 GNU C 最大的特色.我们接下来继续讲一下跟内联函数相关的两个属性:noinline 和 always_inline.这两个属性的用途是告诉编译器:编译时,对我们指定的函数内联展开或不展开.它们的使用方法如下. static inline __attribute__((noinline)) int func(); static inline __att

嵌入式C语言自我修养 07:地址对齐那些事儿

7.1 属性声明:aligned GNU C 通过 attribute 来声明 aligned 和 packed 属性,指定一个变量或类型的对齐方式.这两个属性用来告诉编译器:在给变量分配存储空间时,要按指定的地址对齐方式给变量分配地址.如果你想定义一个变量,在内存中以8字节地址对齐,就可以这样定义. int a __attribute__((aligned(8)); 通过 aligned 属性,我们可以直接显式指定变量 a 在内存中的地址对齐方式.aligned 有一个参数,表示要按几字节对齐

嵌入式C语言自我修养 11:有一种函数,叫内建函数

11.1 什么是内建函数 内建函数,顾名思义,就是编译器内部实现的函数.这些函数跟关键字一样,可以直接使用,无须像标准库函数那样,要 #include 对应的头文件才能使用. 内建函数的函数命名,通常以 __builtin 开头.这些函数主要在编译器内部使用,主要是为编译器服务的.内建函数的主要用途如下. 用来处理变长参数列表: 用来处理程序运行异常: 程序的编译优化.性能优化: 查看函数运行中的底层信息.堆栈信息等: C 标准库函数的内建版本. 因为内建函数是编译器内部定义,主要由编译器相关的

嵌入式C语言自我修养 08:变参函数的格式检查

8.1 属性声明:format GNU 通过 attribute 扩展的 format 属性,用来指定变参函数的参数格式检查. 它的使用方法如下: __attribute__(( format (archetype, string-index, first-to-check))) void LOG(const char *fmt, ...) __attribute__((format(printf,1,2))); 我们经常实现一些自己的打印调试函数.这些打印函数往往是变参函数,那编译器编译程序时