C语言编程需要掌握的知识点和能力

目录

C 语言有多少个关键字,分别都有什么用途?

C 语言的工作机制.

volitile 关键字的作用以及什么时候使用?

结构体和共用体相互嵌套。

位结构体是什么。

在 C 语言的编译体系中,RAM 被怎样划分了?

指针的作用?

函数参数传递,到底传了些什么给被调函数?

什么是回调函数?

const 到底做了些什么?为什么 const 变量不能被写?

全局变量的利与弊,如何控制全局变量?

预处理究竟干了些什么?为什么它如此不负责?

初始化到底有什么作用?怎样才是有效的初始化?

数组和指针的关系?

头文件之间的包含关系。

如何在嵌入式编程中使用位运算?请举例

如何善用 typedef 创造有效的移植代码体系。

使用 void*方式传递内存块时,如何规定好收发方之间的协议(数据类型)。

如何处理常见编译错误的能力。

处理常见编译警告的能力。编译警告清零的能力。

常见的 C 语言的陷阱和缺陷有哪些?

如何掌握防御性编程技巧。

成对编码原则的贯彻是有效编码的基础。

糟糕的代码风格 VS 优秀代码风格。请举例

核心转储(core dump)VS 段错误(Segmentation Fault)VS 各种内存问题。请举例

什么是内嵌汇编,什么时候使用内嵌汇编块。

C 语言有多少个关键字,分别都有什么用途?

C90:32个关键字详解,见下方链接

C语言关键字详解-优快云博客

C99新增五个关键字:inline, restric, _Bool, _Imaginary, _Complex

_Bool, _Imaginary, _Complex关键字解析

inline关键字解析:

inline用于建议编译器将函数内联展开,而不是进行常规的函数调用。

// 内联函数声明
inline int max(int a, int b);

// 内联函数定义
inline int max(int a, int b) {
    return (a > b) ? a : b;
}

// 使用示例
int main() {
    int x = 5, y = 10;
    int result = max(x, y);  // 可能被展开为:int result = (x > y) ? x : y;
    return 0;
}

restric关键字解析:

restrict是一个指针限定符,告诉编译器该指针是访问其所指对象的唯一方式。

// 错误使用 - 违反restrict约定
void wrong_usage(int *restrict a, int *restrict b) {
    // 如果调用时a和b指向相同内存,行为未定义
    a[0] = 10;
    b[0] = 20;  // 违反restrict约定!
}

// 正确的方式
void independent_operations(int *restrict a, int *restrict b) {
    // a和b操作完全独立
    for (int i = 0; i < 100; i++) {
        a[i] = i;
    }
    for (int i = 0; i < 100; i++) {
        b[i] = i * 2;
    }
}

C 语言的工作机制.

嵌入式开发学习——了解c语言运行机制和变量概念(c语言)_c语言的运行机制-优快云博客

  1. 预处理阶段:处理#include指令,包含头文件;展开宏定义,将PI替换为3.14159;处理条件编译指令(#ifdef#ifndef等)

  2. 编译阶段:将预处理后的代码编译为汇编代码
  3. 汇编阶段:将汇编代码转换为目标文件;将汇编指令转换为机器码;生成重定位表、符号表

  4. 链接阶段:将多个目标文件链接为可执行文件

volitile 关键字的作用以及什么时候使用?

volatile int flag = 0;

// 没有volatile时,编译器可能优化为:
while(flag == 0) {
    // 可能被优化为死循环,因为编译器认为flag不会改变
}

// 有volatile时,每次都会从内存读取flag的值

使用场景

场景1:硬件寄存器

// 内存映射的硬件寄存器
volatile unsigned int *status_reg = (unsigned int*)0x1000;
volatile unsigned int *data_reg = (unsigned int*)0x1004;

void wait_for_data() {
    while((*status_reg & 0x01) == 0) {
        // 等待数据就绪,必须从硬件读取最新状态
    }
    unsigned int data = *data_reg;
}

场景2:多线程共享变量

#include <pthread.h>

volatile int shared_counter = 0;

void* thread_func(void* arg) {
    for(int i = 0; i < 1000; i++) {
        shared_counter++;  // 注意:这不是原子操作!
    }
    return NULL;
}

场景3:信号处理

#include <signal.h>

volatile sig_atomic_t signal_received = 0;

void signal_handler(int sig) {
    signal_received = 1;
}

int main() {
    signal(SIGINT, signal_handler);
    
    while(!signal_received) {
        // 正常工作,信号处理会修改signal_received
    }
    printf("Signal received, exiting...\n");
    return 0;
}

结构体和共用体相互嵌套。

共用体是一种特殊的数据类型,允许您在相同的内存位置存储不同的数据类型。您可以定义一个带有多成员的共用体,但是任何时候只能有一个成员带有值。共用体提供了一种使用相同的内存位置的有效方式。

结构体嵌套共用体

// 学生信息,根据类型存储不同数据
struct student {
    int id;
    char name[20];
    int type;  // 1:本科生 2:研究生
    
    union {
        struct {
            char class_name[10];  // 本科生班级
            float gpa;           // 本科生GPA
        } undergraduate;
        
        struct {
            char research_area[30];  // 研究生研究方向
            char supervisor[20];     // 导师
        } graduate;
    } info;
};

// 使用示例
struct student s1;
s1.type = 1;  // 本科生
strcpy(s1.info.undergraduate.class_name, "CS101");
s1.info.undergraduate.gpa = 3.8;

struct student s2;
s2.type = 2;  // 研究生
strcpy(s2.info.graduate.research_area, "Artificial Intelligence");

共用体嵌套结构体

// 数据包解析,共用体根据类型解析不同结构
union data_packet {
    struct {
        unsigned char type;
        unsigned char length;
        unsigned char data[256];
    } raw;
    
    struct {
        unsigned char type;      // 必须为1
        unsigned char length;    // 必须为8
        unsigned int temperature;
        unsigned int humidity;
    } sensor_data;
    
    struct {
        unsigned char type;      // 必须为2
        unsigned char length;    // 必须为4
        unsigned short x;
        unsigned short y;
    } position_data;
};

// 使用示例
union data_packet packet;
memcpy(&packet, received_data, sizeof(packet));

switch(packet.raw.type) {
    case 1:
        printf("Temperature: %u, Humidity: %u\n", 
               packet.sensor_data.temperature, 
               packet.sensor_data.humidity);
        break;
    case 2:
        printf("Position: (%u, %u)\n", 
               packet.position_data.x, 
               packet.position_data.y);
        break;
    default:
        printf("Unknown packet type\n");
}

位结构体是什么。

C 位域 | 菜鸟教程

基本概念:位结构体允许按位分配结构体成员的内存空间,用于节省内存和硬件寄存器操作。

基本语法

struct bit_field {
    type member_name : width;
};

实际应用示例

示例1:状态寄存器

// 32位状态寄存器位域定义
struct status_register {
    unsigned int error_flag    : 1;   // 第0位:错误标志
    unsigned int data_ready    : 1;   // 第1位:数据就绪
    unsigned int busy          : 1;   // 第2位:忙标志
    unsigned int reserved      : 5;   // 第3-7位:保留
    unsigned int mode          : 3;   // 第8-10位:模式选择
    unsigned int channel       : 4;   // 第11-14位:通道选择
    unsigned int              : 17;  // 第15-31位:未命名,填充对齐
};

// 使用示例
struct status_register status;
status.error_flag = 0;
status.data_ready = 1;
status.mode = 5;      // 二进制101
status.channel = 12;  // 二进制1100

printf("Register value: 0x%08X\n", *(unsigned int*)&status);

示例2:IP协议头

// IP协议头位域定义
struct ip_header {
    unsigned int version : 4;     // 版本号
    unsigned int ihl : 4;         // 头部长度
    unsigned int tos : 8;         // 服务类型
    unsigned int total_length : 16; // 总长度
    
    unsigned int identification : 16; // 标识
    unsigned int flags : 3;       // 标志位
    unsigned int fragment_offset : 13; // 分片偏移
    
    unsigned int ttl : 8;         // 生存时间
    unsigned int protocol : 8;    // 协议类型
    unsigned int checksum : 16;   // 头部校验和
    
    unsigned int src_addr;        // 源IP地址
    unsigned int dst_addr;        // 目的IP地址
};

示例3:紧凑数据存储

// 紧凑存储个人基本信息
struct personal_info {
    unsigned int age : 7;         // 0-127岁
    unsigned int gender : 1;      // 0:男, 1:女
    unsigned int education : 3;   // 学历: 0-7
    unsigned int married : 1;     // 婚姻状况
    unsigned int has_car : 1;     // 是否有车
    unsigned int has_house : 1;   // 是否有房
    unsigned int : 18;            // 保留位
};

// 使用示例
struct personal_info person;
person.age = 25;
person.gender = 0;       // 男
person.education = 4;    // 本科
person.married = 0;
person.has_car = 1;
person.has_house = 0;

printf("Size of personal_info: %zu bytes\n", sizeof(person));
// 输出:4 bytes(而不是可能的更多字节)

C 语言的编译体系中,RAM 被怎样划分了?

C语言:内存分配---栈区、堆区、全局区、常量区和代码区_c 内存布局-优快云博客

指针的作用?

函数参数传递,到底传了些什么给被调函数?

什么是回调函数?

C 函数指针与回调函数 | 菜鸟教程

回调函数就是一个通过函数指针调用的函数。

const 到底做了些什么?为什么 const 变量不能被写?

全局变量的利与弊,如何控制全局变量?

全局变量的利:

  1. 方便访问:全局变量可以在程序的任何地方被访问,这使得在多个函数或模块间共享数据变得容易。

  2. 减少参数传递:使用全局变量可以减少函数间参数传递的数量,简化函数调用。

  3. 保持状态:全局变量可以用来保持程序的状态,如配置设置或用户偏好。

全局变量的弊:

  1. 增加耦合度:全局变量增加了模块间的耦合度,使得代码更难理解和维护。

  2. 命名空间污染:全局变量可能导致命名空间污染,增加命名冲突的可能性。

  3. 调试困难:全局变量可以在程序的任何地方被修改,这使得调试变得困难,因为很难追踪变量的修改来源。

  4. 线程安全问题:在多线程环境中,全局变量可能导致线程安全问题,如竞态条件和数据不一致。

  5. 测试难度增加:全局变量使得单元测试变得更加困难,因为测试需要考虑到全局状态的影响。

如何控制全局变量:

  1. 限制使用:尽量减少全局变量的使用,只在确实需要时才使用。

  2. 命名约定:使用明确的命名约定来标识全局变量,如使用g_前缀。

  3. 封装:将全局变量封装在模块或类中,通过接口访问,这样可以控制对全局变量的访问和修改。

  4. 使用配置文件:对于配置设置等全局变量,可以使用配置文件来管理,而不是直接在代码中定义。

  5. 单例模式:对于需要全局访问的对象,可以使用单例模式来控制实例的创建和访问。

  6. 线程同步:在多线程环境中,使用同步机制(如互斥锁)来保护全局变量,避免线程安全问题。

  7. 文档记录:对所有全局变量进行详细的文档记录,说明其用途、生命周期和修改来源。

  8. 代码审查:通过代码审查来监控全局变量的使用,确保它们被合理地使用和管理。

控制全局变量的关键在于谨慎使用,并通过封装、同步和文档等手段来减少它们的负面影响。

预处理究竟干了些什么?为什么它如此不负责?

#define SQUARE(x) x*x
int a = SQUARE(1+2);   // 预处理产出:int a = 1+2*1+2;

预处理器不会帮你加括号,也不会报错,责任后移到编译阶段甚至运行阶段。
设计初衷就是“最快地把源文件剪贴成一份纯 C 代码”,而不是“懂你写啥”。

初始化到底有什么作用?怎样才是有效的初始化?

把对象(变量、数组、结构体、联合体…)在正式使用前置成确定值,避免:

  • 读到垃圾值(未定义行为)

  • 重复赋值带来的性能/逻辑隐患

  • 静态存储期变量默认零初始化以外的意外

数组和指针的关系?

头文件之间的包含关系。

如何在嵌入式编程中使用位运算?请举例

如何善用 typedef 创造有效的移植代码体系。

// portable_types.h
#ifndef PORTABLE_TYPES_H
#define PORTABLE_TYPES_H

#include <stdint.h>

// 明确大小的整数类型
typedef int8_t   s8;
typedef uint8_t  u8;
typedef int16_t  s16;
typedef uint16_t u16;
typedef int32_t  s32;
typedef uint32_t u32;
typedef int64_t  s64;
typedef uint64_t u64;

// 平台相关的通用类型(基于架构自动选择)
#if defined(__x86_64__) || defined(_M_X64)
    typedef int64_t intptr;
    typedef uint64_t uintptr;
    typedef int64_t ssize_t;
#else
    typedef int32_t intptr;
    typedef uint32_t uintptr;
    typedef int32_t ssize_t;
#endif

// 布尔类型(确保C和C++兼容)
#ifdef __cplusplus
    typedef bool bool_t;
#else
    typedef _Bool bool_t;
    #ifndef true
        #define true 1
    #endif
    #ifndef false
        #define false 0
    #endif
#endif

#endif // PORTABLE_TYPES_H

使用 void*方式传递内存块时,如何规定好收发方之间的协议(数据类型)。

如何处理常见编译错误的能力。

处理常见编译警告的能力。编译警告清零的能力。

常见的 C 语言的陷阱和缺陷有哪些?

1. 内存管理陷阱:内存泄漏、悬空指针、双重释放;

2.数组和指针陷阱:数组越界,指针算数错误

如何掌握防御性编程技巧。

防御性编程是一种重要的编程哲学,旨在使代码在遇到意外情况时仍能保持稳定、安全和可预测的行为。

基本原则

  • 不信任任何输入:包括用户输入、文件数据、网络数据、函数参数

  • 明确处理所有错误:不要忽略返回值或错误码

  • 设计容错系统:系统在部分故障时仍能继续运行

  • 保持代码简洁清晰:复杂代码更容易隐藏错误

成对编码原则的贯彻是有效编码的基础。

成对编码原则 是指在编程中,某些操作必须成对出现、成对管理、成对释放的编码规范。这个原则强调资源的对称性管理,确保每个创建操作都有对应的销毁操作,每个获取操作都有对应的释放操作。

糟糕的代码风格 VS 优秀代码风格。请举例

核心转储(core dumpVS 段错误(Segmentation FaultVS 各种内存问题。请举例

段错误是操作系统内存保护机制触发的错误,当程序试图访问其没有权限访问的内存区域时发生。

内存违规访问
    ↓
段错误 (Segmentation Fault) - 信号SIGSEGV
    ↓
核心转储 (Core Dump) - 程序状态的快照
    ↓
调试分析

什么是内嵌汇编,什么时候使用内嵌汇编块。

内嵌汇编(Inline Assembly)是在高级语言(主要是C/C++)代码中直接嵌入汇编指令的技术。它允许开发者在高级语言环境中使用低级的汇编指令,结合两者的优势。z主要目的:1. 性能关键代码优化;

#include <stdint.h>

// 使用内嵌汇编优化内存拷贝
void fast_memcpy(void* dest, const void* src, size_t n) {
    if (n == 0) return;
    
    // 对于小数据使用内嵌汇编可能更快
    asm volatile (
        "cld\n\t"           // 清除方向标志(向前复制)
        "rep movsb"         // 重复移动字节
        : "+D" (dest), "+S" (src), "+c" (n)
        : 
        : "memory"          // 破坏内存内容
    );
}

// 优化的字符串长度计算
size_t fast_strlen(const char* str) {
    size_t len;
    
    asm volatile (
        "xor %0, %0\n\t"    // 清零长度计数器
        "1:\n\t"
        "cmpb $0, (%1, %0)\n\t"  // 检查当前字符是否为0
        "je 2f\n\t"
        "inc %0\n\t"        // 长度加1
        "jmp 1b\n\t"        // 循环
        "2:"
        : "=&r" (len)       // 早期破坏约束
        : "r" (str)
        : "cc", "memory"    // 破坏条件码和内存
    );
    
    return len;
}

2.访问特殊硬件功能

// 读取时间戳计数器(用于高精度计时)
static inline uint64_t read_tsc() {
    uint32_t lo, hi;
    
    // RDTSC指令读取时间戳计数器
    asm volatile (
        "rdtsc"
        : "=a" (lo), "=d" (hi)  // 输出到EAX和EDX
    );
    
    return ((uint64_t)hi << 32) | lo;
}

// 控制CR0寄存器(系统级操作)
void enable_write_protect() {
    unsigned long cr0;
    
    asm volatile (
        "mov %%cr0, %0\n\t"  // 读取CR0
        "or $0x10000, %0\n\t" // 设置写保护位
        "mov %0, %%cr0"      // 写回CR0
        : "=r" (cr0)
        : 
        : "memory"
    );
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

10710

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值