【C语言进阶日记】C语言篇① 盘点嵌入式开发中常见却易忽视的C语言用法

本文聚焦嵌入式开发,剖析了易被忽视的关键技巧。介绍了内联汇编指令,如__SSAT、__QADD等;常见内存操作函数,像memcpy、memmove等;字符串函数,例如strncmp、strlen等;还提及函数修饰符和其他函数或修饰符。掌握这些可提升开发效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文将盘点嵌入式开发中极其常见却极易忽视的内联汇编指令、内存操作函数、函数修饰符。本文是从菜鸟到高手、高手到强手的必经之路!!(注意:本文将持续更新,欢迎点赞关注)


前言

嵌入式开发是一门复杂而精细的技术,除了掌握基本的编程语言和算法知识外,还需要深入了解一些常见却容易被忽视的关键技巧。本文将带您逐一剖析嵌入式开发中经常被忽视的内联汇编指令、内存操作函数和函数修饰符。无论您是初学者还是有经验的开发者,通过学习这些技巧,您将能够在嵌入式开发中更加熟练和高效地应用。。


一、内联汇编指令

内联汇编指令是将汇编代码嵌入到C/C++程序中的一种技术。它可以提供直接访问底层硬件、优化性能和灵活控制的能力。在嵌入式开发中,内联汇编指令经常用于访问特殊寄存器、操作硬件资源等。通过深入学习和理解内联汇编指令的语法和使用方法,您将能够更好地掌握底层的操作和优化技巧。

1.__SSAT

在嵌入式系统中,__SSAT 是一种内联汇编指令,用于执行带饱和运算的整数饱和截断操作。“SSAT” 是 “Signed Saturate” 的缩写,表示有符号饱和截断。

__SSAT 指令的语法通常是 __SSAT(value, bit_width),其中 “value” 是待饱和截断的整数值,“bit_width” 是指定截断后的位宽。该指令将 value 的结果截断到指定的位宽,并进行有符号饱和运算。饱和运算意味着如果结果超出了指定位宽的表示范围,则结果将被限制在最大或最小值上。

例如,如果有一个 8 位的变量 value,其值为 150,如果执行 __SSAT(value, 5),即将其截断为 5 位,那么结果为 15,因为 150 在 5 位有符号表示中超出了范围,被饱和为最大值 15。

请注意,__SSAT 是特定的汇编指令,具体的使用方式和支持程度可能因硬件平台和编译器而有所不同。建议参考特定平台和编译器的文档和指南以获取准确的信息和用法。

2.__QADD

在嵌入式系统中,__QADD 是一种内联汇编指令,用于执行饱和加法操作。“QADD” 是 “Saturating Add” 的缩写,表示饱和加法。

__QADD 指令的语法通常是 __QADD(val1, val2),其中 “val1” 和 “val2” 是待相加的两个操作数。该指令将两个操作数相加,并进行饱和运算。饱和运算意味着如果结果超出了操作数的表示范围,则结果将被限制在最大或最小值上。

例如,如果有两个 8 位的变量 val1 和 val2,分别为 200 和 100,如果执行 __QADD(val1, val2),即进行饱和加法,那么结果为 255,因为 200 + 100 超出了 8 位有符号整数的最大值 127,被饱和为最大值 127。

请注意,__QADD 是特定的汇编指令,具体的使用方式和支持程度可能因硬件平台和编译器而有所不同。建议参考特定平台和编译器的文档和指南以获取准确的信息和用法。

3.__QSUB

在嵌入式系统中,__QSUB 是一种内联汇编指令,用于执行饱和减法操作。“QSUB” 是 “Saturating Subtract” 的缩写,表示饱和减法。

__QSUB 指令的语法通常是 __QSUB(val1, val2),其中 “val1” 和 “val2” 是待相减的两个操作数。该指令将 val1 减去 val2,并进行饱和运算。饱和运算意味着如果结果超出了操作数的表示范围,则结果将被限制在最大或最小值上。

例如,如果有两个 8 位的变量 val1 和 val2,分别为 100 和 200,如果执行 __QSUB(val1, val2),即进行饱和减法,那么结果为 -128,因为 100 - 200 超出了 8 位有符号整数的最小值 -128,被饱和为最小值 -128。

请注意,__QSUB 是特定的汇编指令,具体的使用方式和支持程度可能因硬件平台和编译器而有所不同。建议参考特定平台和编译器的文档和指南以获取准确的信息和用法。

4.__syscall

在嵌入式系统编程中,__syscall 是一种内联汇编指令,用于进行系统调用。系统调用是应用程序与操作系统内核之间进行交互的一种机制,通过系统调用可以请求执行一些特权操作或获取操作系统提供的服务。

__syscall 指令的语法通常是 __syscall(service_number, arguments),其中 “service_number” 是指定要请求的服务号码,而 “arguments” 则是传递给系统调用的参数。

具体的服务号码和参数传递方式取决于所使用的操作系统和体系结构。在典型的嵌入式系统中,常见的系统调用可能包括文件操作、网络通信、进程管理等。通过使用 __syscall 指令,并按照特定的约定传递正确的服务号码和参数,可以实现对系统功能的访问和利用。

请注意,__syscall 是特定的汇编指令,具体的使用方式和支持程度取决于所使用的编译器、操作系统和体系结构。建议参考特定平台和编译器的文档和指南以获取准确的信息和用法。此外,在编写代码时,应注意系统调用的权限和安全性,以避免潜在的风险和错误。

二、内存操作函数

在嵌入式开发中,经常需要对内存进行操作,如复制数据、填充数据等。虽然C/C++语言提供了一些标准库函数来处理内存操作,但对于嵌入式系统来说,使用内联汇编指令来实现内存操作函数能够更高效和灵活。了解并熟练掌握常见的内存操作函数,如memcpy、memset等,将能够在嵌入式开发中提高效率和性能。
常见的内存操作函数包括:

memcpy:用于将源内存区域的内容复制到目标内存区域。
memmove:与memcpy类似,但处理重叠内存区域时更安全。
memcmp:用于比较内存区域的内容。
memset:用于将内存区域设置为指定的值。

1.memcpy()

memcpy 是一个 C 语言中的内存复制函数,用于在内存块之间复制指定数量的字节。函数的原型如下:

void* memcpy(void* dest, const void* src, size_t n);

其中,dest 是目标内存块的起始地址,src 是源内存块的起始地址,n 是要复制的字节数。函数返回值为指向目标内存块的指针。

举个例子,如果你想在内存中复制一段数据,可以这样使用 memcpy 函数:

int src[] = {1, 2, 3, 4, 5};
int dest[5];

memcpy(dest, src, sizeof(src));

// 输出目标内存块中的内容
for (int i = 0; i < 5; i++) {
    printf("%d ", dest[i]);
}
//输出结果为:1 2 3 4 5

需要注意的是,memcpy 函数在执行复制时没有判断源内存块与目标内存块是否重叠,因此如果源内存块与目标内存块重叠,可能导致意想不到的结果。如果需要处理重叠内存操作的情况,可以考虑使用 memmove 函数。另外,memcpy 函数定义在 <string.h> 头文件中。

2.memmove()

memmove 是一个 C 语言中的内存复制函数,用于在内存块之间复制指定数量的字节。与 memcpy 不同的是,memmove 能够处理源内存块与目标内存块有重叠的情况。

函数的原型如下:

void* memmove(void* dest, const void* src, size_t n);

其中,dest 是目标内存块的起始地址,src 是源内存块的起始地址,n 是要复制的字节数。函数返回值为指向目标内存块的指针。

举个例子,如果你想在内存中复制一段数据,可以这样使用 memmove 函数:

int src[] = {1, 2, 3, 4, 5};
int dest[5];

memmove(dest, src, sizeof(src));

// 输出目标内存块中的内容
for (int i = 0; i < 5; i++) {
    printf("%d ", dest[i]);
}
//输出结果为:1 2 3 4 5

需要注意的是,memmove 函数保证在拷贝过程中不会影响内存块的内容,即使源内存块与目标内存块重叠。这使得 memmove 函数更适用于处理重叠内存操作的情况。

注意:memcpy 和 memmove 都是 C 语言中的内存复制函数,用于在内存块之间复制指定数量的字节。它们有一些相似之处,但也有一些关键的区别。

相似之处:

用法相同:memcpy 和 memmove 的参数类型和返回值类型都相同,都是用于复制内存的函数。
数据复制:它们都可以在内存块之间进行数据复制,拷贝指定数量的字节。
区别如下:

内存重叠处理:memcpy 在执行复制操作时没有考虑源内存块与目标内存块是否重叠,因此如果发生重叠,可能导致意外的结果。而 memmove 在处理内存重叠情况时会进行安全处理,可以正确地处理源内存块与目标内存块重叠的情况。
性能:由于 memcpy 不需要处理内存重叠的情况,它可以进行更多的优化,因此在没有内存重叠的情况下,memcpy 的性能可能会比 memmove 更好。但是在有内存重叠的情况下,memmove 的性能可能会略差于 memcpy,因为它需要进行更复杂的处理。
因此,当你确定内存块之间没有重叠时,可以优先选择使用 memcpy,这样可以获得更好的性能。但是如果存在内存重叠的情况,或者不确定是否重叠,建议使用 memmove,这样可以确保数据的正确拷贝。

3.memcmp()

memcmp 是一个 C 语言中的内存比较函数,用于比较两个内存块的内容是否相等。函数的原型如下:

int memcmp(const void* ptr1, const void* ptr2, size_t n);

其中,ptr1 和 ptr2 是要比较的内存块的起始地址,n 是要比较的字节数。函数返回值为整数类型 int,表示比较结果。

举个例子,如果你想比较两个内存块的内容是否相等,可以这样使用 memcmp 函数:

int arr1[] = {1, 2, 3, 4, 5};
int arr2[] = {1, 2, 3, 4, 5};

int result = memcmp(arr1, arr2, sizeof(arr1));

if (result == 0) {
    printf("内存块的内容相等\n");
} else {
    printf("内存块的内容不相等\n");
}
//输出结果为:“内存块的内容相等”

需要注意的是,memcmp 函数会逐字节地比较两个内存块的内容,直到字节数达到指定的数量 n 或者发现不相等的字节。函数返回值为 0 表示相等,负值表示 ptr1 在字典顺序上小于 ptr2,正值表示 ptr1 在字典顺序上大于 ptr2。

4.memset ()

memset 是一个 C 语言中的内存设置函数,用于将一块内存空间设置为指定的值。函数的原型如下:

void* memset(void* ptr, int value, size_t num);

其中,ptr 是要设置的内存块的起始地址,value 是要设置的值,num 是要设置的字节数。函数返回值为指向设置后内存块的指针。

举个例子,如果你想将一块内存空间设置为 0,可以这样使用 memset 函数:

int arr[5];

memset(arr, 0, sizeof(arr));
//这样会将 arr 数组中的每个元素都设置为 0。

需要注意的是,memset 函数是按字节进行设置的,所以在对非字符类型的数组进行设置时需要小心。另外,memset 函数定义在 <string.h> 头文件中。

使用 memset 函数可以快速将内存块设置为指定值,它在某些情况下可以比迭代循环更高效。

三、字符串函数

字符串函数在嵌入式开发中经常用到,例如处理字符串字面量、字符串拼接、字符串比较等。熟练掌握字符串函数可以大大提高代码的可读性和效率。

1.strncmp()

strncmp 是一个 C 语言中的字符串比较函数。它用于比较两个字符串的前 n 个字符是否相等。函数的原型如下:

int strncmp(const char* str1, const char* str2, size_t n);

其中,str1 和 str2 是要比较的字符串,n 是要比较的字符数。函数返回值为 0 表示相等,负值表示 str1 在字典顺序上小于 str2,正值表示 str1 在字典顺序上大于 str2。

举个例子,如果你想比较两个字符串的前 5 个字符是否相等,可以这样使用 strncmp 函数:

char str1[] = "Hello, world!";
char str2[] = "Hello, there!";
int result = strncmp(str1, str2, 5);

if (result == 0) {
    printf("前 5 个字符相等\n");
} else {
    printf("前 5 个字符不相等\n");
}

注意,strncmp 函数会在遇到字符串结束符 \0、达到指定字符数 n 或出现不相等的字符时停止比较。

2.strlen()

strlen是一个 C 语言中的字符串长度函数,用于计算一个字符串的长度(即字符个数)。函数的原型如下:

size_t strlen(const char* str);

其中,str 是要计算长度的字符串。函数返回值为无符号整数类型 size_t,表示字符串的长度。

举个例子,如果你想计算一个字符串的长度,可以这样使用 strlen 函数:

char str[] = "Hello, world!";
size_t length = strlen(str);

printf("字符串的长度是:%zu\n", length);

注意,strlen 函数会在遇到字符串结束符 \0 时停止计数。

3.atoi ()

atoi 是一个 C 语言中的字符串转整数函数,用于将一个字符串转换为相应的整数值。函数的原型如下:

int atoi(const char* str);

其中,str 是要转换的字符串。函数返回值为整数类型 int,表示转换后的整数值。

举个例子,如果你想将一个字符串转换为整数,可以这样使用 atoi 函数:

char str[] = "12345";
int num = atoi(str);

printf("转换后的整数值是:%d\n", num);

需要注意的是,atoi 函数对于非法的字符串输入不进行错误检查,若字符串不能正确转换为整数,结果可能是未定义的。因此,在使用 atoi 函数之前,最好对输入的字符串进行合法性检查。

4.strncmp()

strncmp 是 C 语言标准库 <string.h> 中的一个函数,用于比较两个字符串的前若干个字符是否相等。

函数的原型如下:

int strncmp(const char* str1, const char* str2, size_t n);

其中,str1 和 str2 是要进行比较的两个字符串的指针,n 是要比较的字符数。

strncmp 函数会比较 str1 和 str2 的前 n 个字符。它会按照字典顺序逐个比较字符,直到遇到不相等的字符或比较完 n 个字符。函数的返回值根据比较结果而定:

如果 str1 和 str2 的前 n 个字符完全相等,返回值为 0。
如果 str1 的前 n 个字符在字典顺序上小于 str2 的前 n 个字符,返回值为一个负整数。
如果 str1 的前 n 个字符在字典顺序上大于 str2 的前 n 个字符,返回值为一个正整数。
举个例子,比较两个字符串的前三个字符是否相等:

const char* str1 = "abc";
const char* str2 = "abd";

int result = strncmp(str1, str2, 3);

if (result == 0) {
    printf("前三个字符相等\n");
} else if (result < 0) {
    printf("str1 在字典顺序上小于 str2\n");
} else if (result > 0) {
    printf("str1 在字典顺序上大于 str2\n");
}

输出结果为:

str1 在字典顺序上小于 str2

需要注意的是,strncmp 对比较的字符数 n 是有限制的,超过字符串的实际长度可能会引发访问越界的问题。在使用 strncmp 函数时,请确保传入的字符串长度足够,并避免出现缓冲区溢出等问题。

四、函数修饰符

函数修饰符是一种对函数进行修饰或声明的关键字。在嵌入式开发中,使用适当的函数修饰符可以对代码进行优化和约束,提高可读性和可维护性。例如,使用常用的函数修饰符如inline、noinline、naked等可以实现代码的内联、禁止内联、裸函数等特性,从而提高代码的性能和可控性。

1.__ramfunc

__ramfunc 是一种特殊的函数修饰符,通常在嵌入式系统中使用。它用于将函数放置在特定的 RAM 区域,而不是默认的代码区域(Flash/ROM)。通过将函数放置在 RAM 中,可以实现更快的执行速度和更紧凑的代码。这对于需要高性能和实时响应的应用非常有用,例如实时操作系统和实时控制系统。请注意,__ramfunc 是由特定的编译器提供的编译指令,并不是标准的 C 或 C++ 语法。因此,具体的使用方式和效果可能因编译器和平台而有所差异。如果您需要在具体的项目中使用 __ramfunc,请参考您所使用的编译器和硬件平台的文档和指南。

2.__weak

__weak 是一种函数修饰符,通常用于嵌入式系统中。它用于声明弱符号函数,意味着该函数的定义是可选的,如果有多个相同名称的弱符号函数存在,链接器会选择其中的一个进行链接,但不会发生冲突或错误。

当使用 __weak 修饰符声明一个函数时,如果没有其他地方的强符号函数定义这个函数,那么编译器会为其生成一个默认的实现,通常是一个空函数体。然而,如果在其他地方有强符号函数定义了同名的函数,那么弱符号函数的定义将被忽略,而使用强符号函数的定义。

__weak 修饰符常用于需要在应用程序的不同部分进行扩展或覆盖的地方。例如,在嵌入式系统中,可以使用 __weak 修饰符来声明默认的中断处理函数,然后在需要的地方定义具体的中断处理函数,这样可以方便地进行中断处理的定制和扩展。请注意,__weak 是由特定的编译器提供的编译指令,并不是标准的 C 或 C++ 语法。因此,具体的使用方式和效果可能因编译器和平台而有所差异。如果您需要在具体的项目中使用 __weak,请参考您所使用的编译器和硬件平台的文档和指南。

3.inline

在 C 和 C++ 编程语言中,inline 是一种函数修饰符或关键字,用于指示编译器对函数执行内联展开。

内联函数是在函数调用点处直接将函数的代码插入到调用位置,而不是通过函数调用的方式执行。这可以减少函数调用的开销,提高程序的执行效率。

使用 inline 关键字声明的函数通常是短小的、频繁调用的函数,例如简单的取值或赋值函数。通过将这些函数进行内联展开,可以减少调用函数时的开销,例如函数调用的栈帧保存和恢复、参数传递等,从而提高程序的执行效率。

使用 inline 关键字声明函数时,并不能保证编译器一定会对其进行内联展开,因为编译器对内联展开的执行与优化策略有关。通常,如果函数体较长或者存在复杂的控制流程,编译器可能会选择不进行内联展开。此外,使用 inline 的函数必须在头文件中定义,以便在编译时进行展开。

总之,inline 关键字用于指示编译器对函数进行内联展开,通过减少函数调用的开销提高程序的执行效率。但是具体是否内联展开由编译器决定,可以根据函数的特性和情况来选择使用。

4.attribute((unused))

attribute((unused)) 是一种用于告知编译器变量或函数未使用的属性(attribute)。

在 C 或 C++ 代码中,有时会声明变量或函数,但在实际使用中并没有使用它们。这可能是由于代码重构、注释掉相关代码或尚未完成的功能等原因所导致。然而,编译器可能会给出未使用的变量或函数的警告,这可能对代码的可读性和维护性造成干扰。

为了避免这些未使用的警告,可以使用 attribute((unused)) 属性来告知编译器相应的变量或函数是未使用的。这样,编译器在编译过程中就不会给出相关的警告。

举个例子,在 C 代码中,如果你声明了一个未使用的变量 myVar,你可以使用 attribute((unused)) 来忽略未使用的警告:

int myVar __attribute__((unused));

同样的,在 C++ 代码中,可以这样使用:

int myVar [[gnu::unused]];
这种属性的使用可以帮助减少编译器产生的无用警告,但需要注意的是,unused 属性只适用于未使用的变量或函数,对于其他问题,仍然需要进行适当的错误检查和调试。

5.attribute((packed))

attribute((packed)) 是用于指示编译器以紧凑的方式对齐结构体或联合体成员的 GNU C 扩展特性。

在默认情况下,编译器会根据目标平台的要求对结构体或联合体的成员进行对齐。这意味着在结构体或联合体成员之间可能会存在额外的填充字节,以确保成员按照特定的边界对齐。

使用 attribute((packed)) 特性可以告诉编译器不要进行额外的对齐,使得结构体或联合体成员之间无填充。

例如,考虑以下结构体定义:

struct MyStruct {
    char a;
    int b;
    char c;
};

根据默认的对齐规则,int 类型的成员 b 通常会在 4 字节的边界上对齐,因此 struct MyStruct 的实际内存布局可能是:

| a | - | - | - | b | b | b | b | c |

使用 attribute((packed)) 可以改变成员的对齐方式:

struct __attribute__((packed)) MyStruct {
    char a;
    int b;
    char c;
};

这样,struct MyStruct 的实际内存布局可能变为:

| a | b | b | b | b | c |

需要注意的是,使用 attribute((packed)) 可能会导致一些性能损失,因为无对齐的访问可能会影响某些平台上的性能。此外,对于某些体系结构,无对齐的访问甚至可能导致硬件异常或错误。

attribute((packed)) 特性是 GNU C 扩展,因此它在不同的编译器和平台上的支持程度可能有所不同。如果需要跨平台兼容性,可以使用特定于平台的方式来实现结构体成员对齐控制,例如通过指定编译器的命令行选项或特定的编译指令。

5.attribute((aligned(4)))

attribute((aligned(4))) 是一个编译器特性,用于指定内存对齐方式为 4 字节对齐。

在 C 和 C++ 中,编译器为了优化访问和处理数据的效率,通常会对变量、结构体成员等进行内存对齐。内存对齐是指将数据放置在内存中的地址上,以满足特定的对齐要求。

使用 attribute((aligned(4))) 可以告诉编译器将特定的变量或结构体成员按照 4 字节的边界进行对齐。例如:

struct MyStruct {
    char a;
    int b __attribute__((aligned(4)));
    char c;
};

int main() {
    struct MyStruct s;
    
    printf("%zu\n", sizeof(s));  // 输出 12

    return 0;
}

在上述代码中,int b 被指定为 4 字节对齐。编译器会确保 struct MyStruct 的实例中的成员 b 在内存中的地址是 4 的倍数。因此,sizeof(s) 的结果为 12(而不是根据成员的实际大小计算得到的 9),因为编译器填充了一些额外的字节以满足对齐要求。

需要注意的是,attribute((aligned(4))) 中的对齐值可能受到特定体系结构的限制。对于大多数体系结构,对齐值应为 2 的幂,并且通常受到编译器、目标平台和结构体成员本身大小的限制。

此外,aligned 属性在不同的编译器和平台上的支持程度可能有所不同。如果需要跨平台兼容性,可以使用特定于平台的方式来实现内存对齐控制,例如通过指定编译器的命令行选项或特定的编译指令。

五、其他函数或修饰符

1.abs()

abs 是一个 C 语言中的绝对值函数,用于计算一个整数的绝对值。函数的原型如下:

int abs(int x);

其中,x 是要计算绝对值的整数。函数返回值为整数类型 int,表示 x 的绝对值。

举个例子,如果你想计算一个整数的绝对值,可以这样使用 abs 函数:

int num = -10;
int absoluteValue = abs(num);

printf("绝对值为:%d\n", absoluteValue);
//输出结果为:“绝对值为:10”

需要注意的是,abs 函数的参数和返回值都是整数类型。如果你需要计算浮点数的绝对值,可以使用 fabs 函数。另外,abs 函数定义在 <stdlib.h> 头文件中。

2.func

func 是一个预定义的宏,在C和C++中可以用于获取当前函数的名称作为字符串常量。

使用 func 宏可以方便地在函数内部获取当前函数的名称。例如:

#include <stdio.h>

void myFunction() {
    printf("Current function: %s\n", __func__);
}

int main() {
    myFunction();
    return 0;
}

运行上述代码,将打印出当前函数的名称:

Current function: myFunction

func 提供了一种在编译时获取函数名的方法,因此它不需要运行时的额外开销。它是一个字符串常量,因此不能修改或赋值给其他变量。

请注意,func 是C99和C++标准引入的特性,在较旧的编译器或编译模式中可能不支持。如果需要更高的兼容性,可以考虑使用传统的手动方式来记录和打印函数名。

3.__alignof

__alignof 是一个C/C++编程语言中的关键字,它用于获取给定类型的对齐要求(alignment requirement)。对齐要求表示数据在内存中的位置边界,即它可以告诉编译器如何对数据进行对齐以提高访问效率。

__alignof 运算符用于获取一个类型的对齐要求,它的语法形式为 “__alignof(type)”,其中 “type” 是指定的数据类型。__alignof 返回一个常量表达式,表示指定类型的对齐要求。

例如,如果你想知道一个 int 类型的对齐要求,可以使用 __alignof(int)。这个表达式将返回一个整数值,表示 int 类型数据在内存中的对齐要求。在大多数系统中,int 类型的对齐要求通常为 4 字节。

注意,__alignof 运算符是C/C++的标准扩展,它在不同的编译器中可能会有所不同。因此,在编写跨平台代码时,最好避免直接使用 __alignof,并使用标准的对齐宏(比如 alignof)来代替。

4.__aligned

__aligned 是一个在C/C++编程语言中使用的关键字,用于指定变量或数据结构的对齐要求(alignment requirement)。

__aligned 关键字后面可以跟随一对圆括号,括号内包含对齐要求的字节数。例如,__aligned(4) 表示变量或数据结构需要按照4字节对齐。

对齐要求指定了数据在内存中的对齐边界,即数据存储的起始地址必须是对齐要求的整数倍。这样可以提高数据的访问效率,尤其是在一些特定硬件架构上。

下面是一个示例,展示如何在C语言中使用 __aligned 关键字:

struct MyStruct {
    int a;
    char b;
} __attribute__((aligned(8)));  // 使用 __aligned 指定结构体对齐要求为8字节

int main() {
    struct MyStruct myVar;
    // code...
    return 0;
}

在上面的示例中,通过使用 __aligned 关键字,并将结构体的对齐要求设置为8字节,确保了结构体中的变量按照8字节对齐。这样,myVar 变量的起始地址将会是8的倍数。

需要注意的是,__aligned 关键字是编译器特定的扩展,并不是标准的C/C++语言特性。因此,在使用时应该谨慎,并注意兼容性问题。对于跨平台开发,最好使用标准的对齐宏(如 alignas)来代替 __aligned 关键字。

5.attribute

attribute 是一个在C/C++编程语言中使用的关键字,用于在变量、函数、结构体等实体上添加特定的属性或行为。

attribute 关键字后面可以跟随一对圆括号,括号内包含 attribute 属性声明。attribute 属性可以用于控制编译器的行为,如优化、内存对齐、警告等。不同的编译器支持的 attribute 属性可能略有不同。

以下是几个常见的 attribute 属性的示例:

attribute((aligned(value))):指定实体的对齐要求,其中 value 是对齐要求的字节数。
attribute((packed)):指定结构体或变量不进行字节对齐,以节省内存空间。
attribute((noreturn)):指定函数不会返回,用于告诉编译器在函数返回前不应该生成任何代码。
attribute((deprecated)):标记实体已过时,编译器在使用此实体时会发出警告。
需要注意的是,attribute 是编译器特定的扩展,并不是标准的C/C++语言特性。因此,使用时应该谨慎,并注意兼容性问题。对于跨平台开发,最好使用标准的特性或宏来替代特定编译器的扩展。

6.section

section 是一个在C/C++编程语言中使用的关键字,在变量或函数声明中用于指定它们所属的特定 section(段)。

在嵌入式系统和操作系统开发中,程序的内存布局通常由多个段组成,例如代码段、数据段、只读数据段等。每个段都有特定的属性和访问权限。使用 section 关键字,可以将特定的变量或函数放置在指定的段中。

section 关键字的语法形式为 “section(name)”,其中 name 是你指定的段的名称。通过将变量或函数置于特定的段中,可以实现对内存布局的精确控制,充分利用嵌入式系统的有限资源。

下面是一个示例,展示如何在C语言中使用 section 关键字:

int myVariable __attribute__((section("mySection"))) = 10;

void myFunction() __attribute__((section("mySection")));

int main() {
    // code...
}

在上面的示例中,通过使用 section 关键字,将 myVariable 变量和 myFunction 函数分别放置在名为 “mySection” 的段中。这样,它们就会被链接器放置在相应的段中,实现了对内存布局的精确控制。

需要注意的是,section 关键字是编译器特定的扩展,并不是标准的C/C++语言特性。因此,在使用时应该谨慎,并注意兼容性问题。对于跨平台开发,最好使用标准的特性或宏来实现对内存布局的控制。

_


总结

嵌入式开发中,内联汇编指令、内存操作函数和函数修饰符是常见却易被忽视的关键技巧。深入学习并掌握这些技巧,将使您在嵌入式开发领域更加熟练和高效。无论您是菜鸟还是有经验的开发者,通过不断学习和实践,从菜鸟到高手只是时间问题。相信通过本文的介绍和指导,您将在嵌入式开发的道路上取得更大的成就!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

量子君@极客工作室

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

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

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

打赏作者

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

抵扣说明:

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

余额充值