《C语言内核深度解析》——笔记及拓展(3)

文章是我前几天读了朱有鹏,张先凤老师的《嵌入式Linux与物联网软件开发:C语言内核深度解析》写的,拜读之后,虽没有醍醐灌顶,至少解开了我之前的一些疑惑。

《嵌入式Linux与物联网软件开发:C语言内核深度解析》目录

以前学C语言,就是在IDE上编一下代码,编译器会有错误有警告提示,很少思考过变量、指针、结构体、函数之间,及所编代码和当前所运行系统的关系(系统内存有多大,运行速度怎样,怎样优化算法)。等我真正学了嵌入式,才开始思考上面的问题,才开始去了解软件与硬件的关系。

本文章是边复习边记笔记,跟书籍目录不一样,以后可能会补充及修改。

有纰漏请指出,转载请说明。

学习交流请发邮件 1280253714@qq.com

第五章 组合数据类型(结构体、共用体、枚举)

  • 结构体概述

结构体属于聚合数据类型,结构体变量在被定义后,编译器在编译时会为所有成员分配空间

结构体变量名代表的是整个结构体变量,不像数组名代表地址

#include <stdio.h>

typedef struct {
    char id[8];
    char name[8];
    int age;
}Student;

int main(void){
    Student stu={"2019","ckj",18};
    printf("%p\n",&stu);
    printf("%p\n",&stu.id);
    printf("%p\n",&stu.id[0]);
    printf("%p\n",&stu.id[1]);
    printf("%p\n",&stu.age);    
    return 0;
}
  • 向函数传递结构体

向函数传递结构体时,实际上是传递结构体成员的值,但是如果定义的结构体是多成员结构或者有数组的结构时,把整个结构体变量进行压栈,运行性能会恶化。解决办法是向函数传递结构体指针,压栈的仅仅是结构体变量的地址

#include <stdio.h>

typedef struct {
    char id[8];
    char name[8];
    int age;
}Student;

/*
函数addAge1被调用时,在函数栈中开辟stu1的变量空间
可看做复制stu给stu1,实际上操作的是stu1与main函数的stu无关
*/
Student addAge1(Student stu1){
    stu1.age++;
    return stu1;
}

/*
指针stu2指向stu
即在addAge2中直接操作变量stu
*/
void addAge2(Student* stu2){
    (stu2->age)++;    
}
int main(void){
    Student stu={"2019","ckj",18};
    addAge1(stu);
    printf("%d\n",stu.age);            //18
    addAge2(&stu);
    printf("%d\n",stu.age);            //19
    printf("%d\n",addAge1(stu).age);   //20 仅仅把返回值输出而已,实际上stu.age还是19
    printf("%d\n",stu.age);            //19
    return 0;
}
  • 结构体对齐访问

结构体元素的偏移量要考虑元素的对齐访问,结构体实际占用的字节数与所有成员占用的字节数的总和不一定相等。

32位系统的内存,每次按照4个字节对齐访问,效率是最高的。对齐访问牺牲了内存空间,换取速度性能。

//编译器默认4字节对齐
#include <stdio.h>

/*
#pragma pack(1) //手动1字节对齐
*/

typedef struct 
{            //1字节对齐    4字节对齐
    int a;   // 4            4
    char b;  // 1            2
    short c; // 2            2
}MyStruct;

int main(void){
    MyStruct s1;
    printf("%d \n",sizeof(s1));    //8
    return 0;
}
  • 共用体(同一个内存空间的多种解析方式)

union类型声明、变量定义和使用

#include <stdio.h>

/*
i、c、和f指向同一块内存,地址一样,只是对这块内存的不同解析方式
*/
typedef union 
{
    int i;
    char c;
    float f;
}MyUnion;

int main(void){
    MyUnion u1;
    u1.i=321;

    printf("%d \n",u1.i);    //321
    printf("%c \n",u1.c);    //A     'A'的ASCII码为65,321-256=65
    printf("%f \n",u1.f);    //0.000000

    printf("%p \n",&u1.i);    //012FFA1C
    printf("%p \n",&u1.c);    //012FFA1C
    printf("%p \n",&u1.f);    //012FFA1C
    return 0;
}

用指针和强制类型转换可以替代共用体完成同样的功能,但是共用体更好理解

  • 大小端模式

51单片机用大端,ARM大部分是小端

大端:高字节对应低地址

小端:高字节对应高地址

用union测试机器的大小端模式
#include <stdio.h>

typedef union 
{
    int i;
    char c;
}MyUnion;

int is_little_endian1(MyUnion u2)
{
    u2.i=1;
    return u2.c;
}

int is_little_endian2(MyUnion* u2)
{
    u2->i=1;
    return u2->c;
}

int main(void){
    MyUnion u1;

    if(1==is_little_endian2(&u1)){
    //if (1==is_little_endian1(u1)){  
        printf("小端模式 \n");
    }else{
        printf("大端模式 \n");
    }
    return 0;
}
#include <stdio.h>

typedef union 
{
    int i;
    char c;
}MyUnion;

int is_little_endian1(MyUnion u2)
{
    u2.i=0x12345678;
    return u2.c;
}

int main(void){
    MyUnion u1;

    printf("%x \n",is_little_endian1(u1)); //78
    return 0;
}
用指针测试机器的大小端模式
#include <stdio.h>

int is_little_endian1(void)
{
    int a=1;
    char b=*(char *)&a;
    return b;
}

int main(void){
    if(1==is_little_endian1()){
        printf("小端模式 \n");
    }else{
        printf("大端模式 \n");
    }
    return 0;
}
#include <stdio.h>

int main(void){
    int a=0x12345678;
    char b=*(char *)&a;
    printf("%x \n",b);    //78
    return 0;
}
#include <stdio.h>

int is_little_endian1(char *p){
    return *p;
}

int main(void){
    int a=0x12345678;
    printf("%x \n",is_little_endian1((char *)&a)); //78
    return 0;
}
  • 练习(写出三次打印输出的结果)

#include <stdio.h>
#include <stdlib.h>

void *MEN_ADDR;

int main(void){
    int *pint_addr=NULL;
    short *pshort_addr=NULL;
    char *pchar_addr=NULL;

    MEN_ADDR=(void *)malloc(sizeof(int));
    pint_addr=(int *)MEN_ADDR;
    pshort_addr=(short *)MEN_ADDR;
    pchar_addr=(char *)MEN_ADDR;

    *pint_addr=0x12345678;
    printf("0x%x \t\t 0x%x \n",*pshort_addr,*pchar_addr);

    pshort_addr++;
    *pshort_addr=0x5555;
    printf("0x%x \t 0x%x \n",*pint_addr,*pchar_addr);

    pchar_addr++;
    *pshort_addr=0xAA;
    printf("0x%x \t 0x%x \n",*pint_addr,*pchar_addr);

    free(MEN_ADDR);
    return 0;
}
/*
0x5678           0x78
0x55555678       0x78
0xaa5678         0x56
*/
  • 枚举enum

为什么需要枚举

枚举就是对0、1这些数字进行符号化编码,符号的意义显而易见,而数字需要查看文档或注释

枚举的规则

枚举是int类型的常量集,自动从0开始

用户自定义了一个值,则从定义的那个值开始往后依次增加

  • 课后题

2 计算file占用内存大小

#include <stdio.h>

int main(void){
    char *file[]= {"f1","f2","f3","f4"};
    printf("%d",sizeof(file));    //16
    return 0;
}

7 对字符串进行逆序操作

//指针操作
#include <stdio.h>
#include <string.h>

void reverseStr(char *ps){
    int i=0;
    char tmp;
    for (i = 0; i < strlen(ps)/2; i++){
        tmp=*(ps+(strlen(ps)-i-1));
        *(ps+(strlen(ps)-i-1))=*(ps+i);
        *(ps+i)=tmp;
    }
}

int main(){
    char str[]="hello";
    printf("%s \n",str);
    reverseStr(str);
    printf("%s \n",str);
    return 0;
}
//直接对数组操作
#include <string.h>

void swap(char *a,char *b){
 char tmp;
 tmp=*b;
 *b=*a;
 *a=tmp;
}

int main(void){
    int i=0;
    char str[]="part";
    printf("%s \n",str);
    for (i = 0; i < strlen(str)/2; i++){
        swap(&str[i],&str[strlen(str)-i-1]);
    }
    printf("%s \n",str);
    return 0;
}

11 统计字符串有多少个单词(单词之间用空格或制表符隔开)

/*制表符进了csdn的代码编辑区会变成空格o(╥﹏╥)o*/
#include <stdio.h>

int calNunOfWord(char *ps){
    int i=0;
    int n=0;
    printf("%s \n",ps);
    while(*(ps+i) != '\0'){
        if((*(ps+i)==9)||(*(ps+i)==32)){    //ascii 制表符9 空格32 
            n++;
        }
        i++;
    };
    return n;
}

int main(void){
    char str[]="    If I were you, I will give up!";
    printf("%d \n",calNunOfWord(str));
    return 0;
}

第六章 C语言的预处理、函数和函数库

  • 编译链接流程

hello.c 文件

#include <stdio.h>
#define PI 3.14
int main(void){
    printf("%f \n",2*PI*3);
    return 0;
}
过程一 预处理(cpp)

>> gcc -E hello.c -o hello.i

生成 hello.i 文件

预处理涉及内容

1 文件包含

2 宏定义

3 条件编译

4 预处理关键字

5 去掉程序中的注释

预处理指令如

#include #ifdef #define宏

在预处理过程中,将PI替换成3.14

过程二 编译(cc)

>> gcc -S hello.i -o hello.s

生成 hello.s 文件

    .file    "hello.c"
    .text
    .def    ___main;    .scl    2;    .type    32;    .endef
    .section .rdata,"dr"
LC1:
    .ascii "%f \12\0"
    .text
    .globl    _main
    .def    _main;    .scl    2;    .type    32;    .endef
_main:
LFB13:
    .cfi_startproc
    pushl    %ebp
    .cfi_def_cfa_offset 8
    .cfi_offset 5, -8
    movl    %esp, %ebp
    .cfi_def_cfa_register 5
    andl    $-16, %esp
    subl    $16, %esp
    call    ___main
    fldl    LC0
    fstpl    4(%esp)
    movl    $LC1, (%esp)
    call    _printf
    movl    $0, %eax
    leave
    .cfi_restore 5
    .cfi_def_cfa 4, 4
    ret
    .cfi_endproc
LFE13:
    .section .rdata,"dr"
    .align 8
LC0:
    .long    1030792151
    .long    1077073674
    .ident    "GCC: (MinGW.org GCC Build-2) 9.2.0"
    .def    _printf;    .scl    2;    .type    32;    .endef
过程三 汇编(as)

>> gcc -c hello.s -o hello.o

生成 hello.o 文件

过程四 链接(ld)

>> gcc hello.o -o hello.out

生成 hello.out文件

GCC常见拓展名

拓展名

含义

.c

是C语言源代码文件

.a

由目标文件构成的档案库文件(静态库文件)

.C、.cc、.cpp、.C++、.cp、.cxx

C++源码文件

.h

程序包含的头文件(适用于C,C++,Objective-C,Objective-C++)

.i

已经预处理过的C源代码文件

.ii

已经预处理过的C++源代码文件

.m

Objective-C源代码文件(Note that:必须和“libobjs”库相链接,才能保证Objective-C能正常工作)

.o

编译后的目标文件

.s

汇编语言源代码文件

.S

经过预编译的汇编语言源代码文件

生成可执行文件

>> gcc hello.c -o hello

生成 hello.exe 文件

  • 库函数

库函数直接调用就好,我们只关心输入参数和输出参数

C语言标准库函数大全(ctype、time 、stdio、stdlib、math、string)

  • man手册

什么是man手册

什么是man手册

man手册就是linux下提供给用户查看linux语法的一本书,当用户遇到一些不懂的命令/函数的时候,不用刻意地去记住命令/函数是什么,只需要记住怎么在man手册中查询即可。

遇到不懂的命令 ---> 立即查询man手册 --> 得知: 命令功能、命令参数、命令使用方法。

遇到不懂的函数 ---> 立即查询man手册 --> 得知: 函数功能、函数头文件、函数原型、函数参数、函数返回值。

>> man man

>> man 3 printf

>> man chmod

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值