C标准

C语言标准

C89

C89标准,也被称作ANSI C,是C语言编程语言的第一个国际标准。它在1989年由美国国家标准协会(ANSI)发布,标准编号为ANSI X3.159-1989。C89标准定义了C语言的基本语法、数据类型、运算符、控制结构、函数、存储类别以及预处理指令等内容。

C89标准主要包括以下内容:

  1. 基本数据类型:包括charshortintlong以及浮点类型floatdouble等。
  2. 运算符和表达式:包括算术运算符、逻辑运算符、关系运算符和位运算符等。
  3. 控制结构:如ifwhileforswitch语句等。
  4. 函数:包括函数的声明、定义和调用等。
  5. 存储类别:如autoregisterstaticextern等。
  6. 预处理指令:例如#define#include等。

C89标准在1990年被国际标准组织ISO(International Organization for Standardization)和国际电工委员会(IEC)采纳为国际标准,命名为ISO/IEC 9899:1990,也被称为C语言的国际标准。尽管C89标准已经有些年头,但它仍然是许多编译器所支持的标准,并且在一些嵌入式系统和旧的软件项目中仍然被广泛使用。

然而,C89标准也存在一些限制和不足,例如不支持长标识符、不支持可变参数函数、不支持单行注释等。为了解决这些问题并扩展C语言的功能,后来的标准如C99和C11对C89进行了扩充和更新。C99标准引入了更多的特性,如支持长标识符、支持单行注释、引入了新的数据类型(如_Complex_Bool)以及新的库函数等。C11标准则进一步扩展了C语言的功能,包括对多线程的支持、新的原子操作以及对Unicode的支持等。

C99

--std=c99

C99标准,也被称为ISO/IEC 9899:1999,是C语言编程语言的第二个国际标准,于1999年发布。它是在C89标准的基础上进行了扩展和增强,增加了许多新的特性和功能。C99标准得到了广泛的接受和应用,成为现代C语言编程的重要基础。

C99标准的主要改进和增强包括:

  1. 增加restrict指针

C99引入了指针类型限定符restrict,它用于告诉编译器两个指针不会指向同一块内存区域,从而帮助编译器进行优化。

// 受限指针
// 开优化之后,编译器会认为两个指针不会指向同一块内存区域
// 不开优化,没有区别。都返回结果:10
int add_fun(int *a, int *b)
{
    *a = 2;
    *b = 5;
    return *a + *b;
}

int add_fun2(int * restrict a, int * restrict b)
{
    *a = 2;
    *b = 5;
    return *a + *b;
}

int main(int argc, char *argv[])
{
    int x = 0;

    int *a = &x;
    int *b = &x;
    printf("r = %d\n", add_fun(a, b));
    printf("r = %d\n", add_fun2(a, b));
    return 0;
}

// gcc c99.c --std=c99 -O2
// 执行
// r = 10
// r = 7
  1. inline 内联函数
  2. 新增数据类型
类型描述说明
_Bool布尔类型新增stdbool.h头文件,定义了bool,true,false
_Complex复数类型新增complex.h头文件,定义了_Complex,I
long long int64位整数新增stdint.h头文件,定义了int8_t,uint32_t,int64_t
long double扩展精度浮点数新增float.h头文件,定义了`FLT_EVAL
  1. 可变长数组 只有局部数组可变长。这里的可变长数组指的是数组的长度可以由运行时决定,而不是在编译时确定。 注意:可变长数组在生存期内是不变的。并非是动态的,可变的只是数组的长度。
#include <stdio.h>

void reverse(int *data, int len)
{
    int temp[len];
    // 实际中肯定不会这么写,只是为了测试
    for(int i = 0; i < len; i++)
    {
        temp[i] = data[len - i - 1];
    }

    for(int i = 0; i < len; i++)
    {
        data[i] = temp[i];
    }
}

void print_array(int *data, int len)
{
    for(int i = 0; i < len; i++)
    {
        printf("%d ", data[i]);
    }
    printf("\n");
}

int main(int argc, char *argv[])
{
    int a[] = {1,2,3,4,5};
    print_array(a, 5);
    reverse(a, 5);
    print_array(a, 5);
    return 0;
}
  1. //单行注释 有些编译器即使C89也支持了,实测gcc的--std=c89不可以。

  2. 预处理程序的修改

  • 变元列表
#include <stdio.h>

#define DBG(...) printf(__VA_ARGS__)

int main(int argc, char *argv[])
{
    int a = 10;
    int *p = &a;
    *p = 10;
    DBG("only test\n");
    DBG("a = %d\n", a);
    return 0;
}
  1. 复合赋值 结构体赋值
struct _student
{
	char name[16];
	int id;
};
struct _student student = {"robot", 97};
struct _student temp;

temp = student;
temp = (struct _student){"robot", 97};

数组不可以直接赋值,编译器报错。

int a[5] = {1,2,3,4,5};
int data[5] = {0};
/* 
    data = (int []){10,20,30,40,50};
    ata = a;
*/

// 指针可以,但似乎和直接定义一个数组没区别啊
int *p = NULL;
p = (int []){1,2,3,4,5};        // 这有什么意义吗?

int p2[] = {1,2,3,4,5};
  1. for循环 可以在for循环中定义一个或多个变量,作用域仅为for循环体。
for(int i = 0; i < 10; i++)
{
    // ...
}
  1. 柔性数组成员 结构体中的最后一个成员允许是未知大小的数组(可变大小的数组),称为柔性数组成员。但结构体柔性数组成员前至少要有一个其他成员。sizeof该结构体,返回的时候不包括柔性数组成员的大小。
// 若在c89某些编译器上,不支持该功能,你又想用
// 那么可以data[1],然后计算结构体的时候,在某些特定场合计算大小,可能需要sizeof(struct _apdu)-1而已
#include <stdint.h>
#include <stdio.h>

struct _apdu
{
    uint8_t cla;
    uint8_t ins;
    uint8_t p1;
    uint8_t p2;
    uint8_t lc;
    uint8_t data[];     // uint8_t data[0];
};

int main(int argc, char *argv[])
{
    printf("len = %ld\n", sizeof(struct _apdu));
    return 0;
}
// len = 5
  1. 指定的初始化符 成员初始化
int data[10] = {[3]=5, 1, [8]=9};
// data[3] = 5, data[4] = 1, data[8] = 9, 其他为0

int data[10] = {
    [1] = 1,
    [2] = 2,
    [3 ... 5] = 4,
    [8] = 8,
    [9] = 9,
};
// data:0 1 2 4 4 4 0 0 8 9

struct _apdu apdu = {.ins=0x84, .lc=0x10};
// apdu: 00 84 00 00 10
  1. printf和scanf增强 支持了ll,支持long long int类型。 支持了hh,用于char型变元

  2. 新增头文件

参考C语言标准库

  1. __func__预定义符
  2. 其他特性
  • 放宽限制:数据块嵌套层数,条件语句嵌套层数,函数调用参数个数,结构体成员个数等
  • 返回值:非空类型的函数,必须使用带有返回值的return语句
  • 扩展的整数类型,如int16_t,int_least16_t, int_fast32_t, uintmax_t
  • 整数类型提升规则修改,如long long int高于int

C11

C11标准,全称为ISO/IEC 9899:2011,也被称为C1X,是C语言的第三个国际标准,于2011年12月发布。它是在C99标准的基础上进行了进一步的扩展和增强,增加了一些新的特性和功能。C11标准的目标是提供更高的性能和更好的可移植性,同时保持对早期C语言标准的兼容性。

C11标准的主要改进和增强包括:

  1. 对齐处理(Alignment)的标准化:C11引入了对齐处理的标准化,包括_Alignas标志符、alignof运算符、aligned_alloc函数以及<stdalign.h>头文件。这些特性使得程序员能够更精确地控制数据的对齐方式,从而提高程序的性能和可移植性。
  2. _Noreturn函数标记:C11引入了_Noreturn函数标记,用于标识一个函数不会返回。这与GCC的__attribute__((noreturn))类似,可以帮助编译器进行更好的优化。
  3. _Generic关键字:C11引入了_Generic关键字,用于实现泛型编程。它允许程序员根据表达式的类型选择不同的函数或操作,增强了C语言的灵活性。
  4. 多线程(Multithreading)支持:C11增加了对多线程的支持,包括_Thread_local存储类型标识符、<threads.h>头文件(里面包含了线程的创建和管理函数)以及原子操作相关的类型和函数(如<stdatomic.h>头文件中的_Atomic类型修饰符)。这些特性使得C语言能够更方便地编写并发程序。
  5. 增强的Unicode支持:C11增加了对Unicode的支持,包括对UTF-8和UTF-16编码的处理以及新的Unicode字符分类函数。这使得C语言能够更好地处理国际化文本。
  6. 更多的浮点处理宏和函数:C11增加了一些新的浮点处理宏和函数,用于处理浮点数和复数运算。这些特性提高了C语言在数值计算领域的适用性。
  7. 匿名结构体/联合体支持:C11将GCC早已支持的匿名结构体/联合体特性引入标准。这允许程序员在结构体或联合体中定义匿名的成员,从而简化代码结构。

C11标准在现代C语言编程中得到了广泛关注和应用。尽管在实际项目中,由于编译器和平台的支持情况,C99或更早的标准仍然可能被使用,但C11标准提供的新特性和功能无疑为C语言编程带来了更多的便利和灵活性。当前,许多主流编译器如GCC、Clang、Intel C++ Compiler等都支持C11标准。

C语言标准库

编号头文件功能说明C89C99C11C17C2x
1<assert.h>宏定义和assert函数,用于调试目的
2<complex.h>复数类型定义和函数新增
3<ctype.h>字符处理函数,如判断字符类型、大小写转换等
4<errno.h>错误码宏定义,用于检测库函数调用中的错误
5<fenv.h>浮点环境函数,控制浮点数的异常和舍入模式新增
6<float.h>浮点类型属性,如浮点数的范围、精度等
7<inttypes.h>整数类型格式转换宏,用于printf和scanf等函数新增
8<iso646.h>替代运算符宏,如andornot新增
9<limits.h>各种数据类型属性,如整数的最大值和最小值
10<locale.h>本地化函数和宏,用于处理不同地域的语言和文化习惯
11<math.h>数学函数和宏,如三角函数、指数函数等
12<setjmp.h>非局部跳转函数,用于setjmp和longjmp
13<signal.h>信号处理函数,用于处理程序运行时接收到的信号
14<stdarg.h>可变参数宏和类型,用于处理函数中的可变参数列表
15<stdbool.h>布尔类型定义,包括bool、true和false新增
16<stddef.h>通用类型定义和宏,如NULL、ptrdiff_t等
17<stdint.h>固定宽度整数类型定义,如int8_t、uint32_t等新增
18<stdio.h>输入输出函数,如printf、scanf、fopen等
19<stdlib.h>通用函数,如内存分配、随机数生成、程序终止等
20<string.h>字符串处理函数,如复制、连接、比较等
21<tgmath.h>类型泛型数学函数,提供统一接口用于处理不同类型的数学运算新增
22<time.h>时间处理函数,如获取当前时间、格式化时间等
23<wchar.h>宽字符处理函数,用于处理多字节字符集和宽字符集C95新增
24<wctype.h>宽字符分类函数,如判断宽字符的类型C95新增

assert

assert.h头文件定义了两个宏:assertNDEBUG

  • assert宏用于在程序中添加断言,如果断言失败,则终止程序并输出错误信息。
  • NDEBUG宏用于控制是否启用断言。如果定义了NDEBUG,则不会启用断言;否则会启用断言。
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <float.h>

double division(double numerator, double denom)
{
    // assert
    // true: 继续执行
    // false: 打印错误信息
    assert(!(denom <= DBL_EPSILON));    // denom == 0则报错
    
    return numerator / denom;
}

int main() {
    double result = 0;
    result = division(5, 2);
    printf("result = %f\n",result);
    result = division(10, 0);
    printf("result = %f\n",result);
    return 0;
}

complex

C99标准开始支持complex.h,定义了一系列用于处理复数的函数和宏。

函数名声明描述
cabsdouble cabs(double complex z);计算复数z的模(绝对值)。
cacosdouble complex cacos(double complex z);计算复数z的反余弦值。
casindouble complex casin(double complex z);计算复数z的反正弦值。
catandouble complex catan(double complex z);计算复数z的反正切值。
catanhdouble complex catanh(double complex z);计算复数z的反双曲正切值。
ccosdouble complex ccos(double complex z);计算复数z的余弦值。
ccoshdouble complex ccosh(double complex z);计算复数z的双曲余弦值。
cexpdouble complex cexp(double complex z);计算复数z的指数函数值。
clogdouble complex clog(double complex z);计算复数z的自然对数。
conjdouble complex conj(double complex z);获取复数z的共轭。
cprojdouble complex cproj(double complex z);计算复数z在Riemann球上的投影。
crealdouble creal(double complex z);获取复数z的实部。
cimagdouble cimag(double complex z);获取复数z的虚部。
cpowdouble complex cpow(double complex x, double complex y);计算复数x的y次幂。
csindouble complex csin(double complex z);计算复数z的正弦值。
csinhdouble complex csinh(double complex z);计算复数z的双曲正弦值。
csqrtdouble complex csqrt(double complex z);计算复数z的平方根。
ctandouble complex ctan(double complex z);计算复数z的正切值。
ctanhdouble complex ctanh(double complex z);计算复数z的双曲正切值。

ctype

包含了用于测试和映射字符的函数

函数名功能描述
isalnum()检查字符是否为字母或数字
isalpha()检查字符是否为字母
isascii()检查字符是否为ASCII字符
iscntrl()检查字符是否为控制字符
isdigit()检查字符是否为数字
isgraph()检查字符是否为可打印字符(不包括空格)
islower()检查字符是否为小写字母
isprint()检查字符是否为可打印字符(包括空格)
ispunct()检查字符是否为标点符号
isspace()检查字符是否为空白字符(如空格、制表符、换行符等)
isupper()检查字符是否为大写字母
isxdigit()检查字符是否为十六进制数字
tolower()将大写字母转换为小写字母
toupper()将小写字母转换为大写字母

errno

提供了全局变量errno,当系统调用或库函数发生错误时,这个变量通常会被设置为特定的错误码。

errno通常是一个 int 类型的变量,可以被程序读取和修改。

同时,该头文件也定义了一系列的宏,这些宏通常用于表示系统调用或库函数在发生错误时设置的错误码。

宏名称描述
EDOM表示数学函数的参数超出其定义域时的错误。例如,尝试对负数求平方根时。
ERANGE表示数学函数的结果超出其返回类型能够表示的范围时的错误。例如,整数溢出。
EILSEQ表示不合法的字符序列错误,通常与多字节字符编码有关。
EINVAL表示无效的参数传递给函数时的错误。
ENOMEM表示内存分配失败,因为没有足够的可用内存。
EACCES表示权限不足,无法访问文件或资源。
ENOENT表示文件或目录不存在。
EIO表示输入/输出错误,通常与硬件故障或驱动程序问题有关。
EBADF表示无效的文件描述符。
EXDEV表示跨设备链接错误,例如尝试在不同文件系统的文件之间创建硬链接。

示例:

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

int main() {
    double number = -1;
    double result = 0;
    
    // errno 中的全局变量
    errno = 0;
    result = sqrt(number);
    
    if(errno != 0)
    {
        printf("#error\n");
    }
    else
    {
        printf("result = %f\n", result);
    }

    if(errno == EDOM)
    {
        printf("Domain Error!\n");
    }
    
    if(errno == ERANGE)
    {
        printf("Range Error!\n");
    }
    return 0;
}

math

函数名声明描述
acosdouble acos(double x);计算x的反余弦值,结果以弧度表示。
asindouble asin(double x);计算x的反正弦值,结果以弧度表示。
atandouble atan(double x);计算x的反正切值,结果以弧度表示。
atan2double atan2(double y, double x);计算从x轴到点(x, y)的向量之间的角度,结果以弧度表示。
ceildouble ceil(double x);返回不小于x的最小整数。
cosdouble cos(double x);计算x的余弦值,x以弧度为单位。
coshdouble cosh(double x);计算x的双曲余弦值。
expdouble exp(double x);计算e的x次方。
fabsdouble fabs(double x);返回x的绝对值。
floordouble floor(double x);返回不大于x的最大整数。
fmoddouble fmod(double x, double y);计算x除以y的余数。
frexpdouble frexp(double value, int *exp);将浮点数分解为尾数和指数。
hypotdouble hypot(double x, double y);计算直角三角形的斜边长度。
ldexpdouble ldexp(double x, int exp);返回x乘以2的exp次方的值。
logdouble log(double x);计算x的自然对数(以e为底)。
log10double log10(double x);计算x的常用对数(以10为底)。
modfdouble modf(double value, double *ipart);将浮点数分解为整数部分和小数部分。
powdouble pow(double base, double exponent);计算base的exponent次方。
sindouble sin(double x);计算x的正弦值,x以弧度为单位。
sinhdouble sinh(double x);计算x的双曲正弦值。
sqrtdouble sqrt(double x);计算x的平方根。
tandouble tan(double x);计算x的正切值,x以弧度为单位。
tanhdouble tanh(double x);计算x的双曲正切值。

根据需要链接数学库(如-lm)。对于涉及到浮点数的运算,要注意浮点数的精度和舍入行为可能导致的误差,以及避免除以零等可能导致程序崩溃的情况。

#include <stdint.h>
#include <stdio.h>
#include <math.h>

int main(void)
{
    double a = 2;
    double result = 0;
    
    printf("%f\n",cos(3.14));

    // result = pow(a,5);
    // printf("%f\n",result);

    return 0;
}

那么为什么只有pow(a,5)的时候需要连接数学库,而cos(3.14)不需要呢?

gcc -S test.c 
cat test.s | grep 'cos'
cat test.s | grep 'pow'

通过汇编代码分析,只找到了pow,没有发现cos。因为传递的参数是常量,编译器可以优化。就直接用数值替代了。根本没有调用cos函数。

setjmp

主要作用是实现一种非局部跳转机制,允许程序在深层嵌套的函数调用中直接返回到某个特定的setjmp点,而不是按照常规的函数调用和返回顺序执行。

函数作用
int setjmp(jmp_buf env)这个宏用于设置跳转点。它保存当前的程序上下文到env参数中,并返回0。如果setjmp是直接调用的,它将返回0;但如果在longjmp调用之后调用,它将返回非0值。
void longjmp(jmp_buf env, int val)这个函数用于实现非局部跳转。它使用env参数中保存的上下文信息,将程序的控制流跳转到最近一次调用setjmp的地方。同时,它还将val作为setjmp的返回值。

jmp_buf这是一个数据类型,通常是一个数组或结构体,用于保存程序执行时的上下文信息,如程序计数器、栈指针等。

这些信息在setjmp调用时被保存,供longjmp在跳转时使用。

示例:

/**
    该示例模拟异常抛出的场景
*/
#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>

jmp_buf jmp_env;

double fun_div0(double data, double div)
{
    if(div == 0)
    {
        // ------------------------------
        // 即便这里填写0,实际上传的是1。但传其他值正常。
        // void longjmp(jmp_buf env, int val)
        // val == 0 ? setjmp(env) == 1 : setjmp(env) == value
        // ------------------------------
        // longjmp(jmp_env, 0); 
        // longjmp(jmp_env, -1);
        longjmp(jmp_env, 5); 
    }
    return data/div;
}

int main()
{
    double result = 0;
    int ret = 0;
    
    // 保存现场
    ret = setjmp(jmp_env);
    printf("ret = %d\n", ret);
    if(ret == 0)
    {
        // 如果 setjmp 返回 0,说明没有发生跳转
        result = fun_div0(5, 2);
        printf("1. result = %0.2f\n",result);
        
        result = fun_div0(5, 0);
        printf("2. result = %0.2f\n",result);
    }
    else
    {
        printf("#error: Dividing by zero is an illegal operation.\n");
        exit(-1);
    }

    printf("----- main exit -----\n");
    return 0;
}

signal

signal.h提供了信号(即异常情况)的处理工具。以下是这个头文件中定义的一些主要函数和宏:

函数

函数名描述
int signal(int signum, sig_t handler)设置信号处理函数。当指定的信号signum发生时,调用handler函数。
int raise(int sig)该函数会生成信号sig

sig_t是一个函数指针类型,用于指向信号处理函数。

sig_atomic_t是一个数据类型,在信号处理程序中作为变量使用,它保证即使在存在异步信号的情况下,对该变量的访问也是原子的。

宏名描述
SIGABRT异常中止信号,可能由于调用了abort()方法。
SIGFPE浮点异常信号,如除以零或溢出。
SIGILL非法指令信号,如执行了无效的机器代码。
SIGINT中断信号,通常由用户按下Ctrl+C产生。
SIGSEGV无效内存引用信号,如访问未分配的内存。
SIGTERM终止信号,要求程序正常退出。
SIG_DFL默认的信号处理程序宏,表示使用系统默认的信号处理方式。
SIG_ERR表示信号错误,当信号相关函数执行失败时返回此值。
SIG_IGN忽略信号的处理程序宏,表示忽略指定的信号。

注意:具体的信号种类和处理方式可能因操作系统的不同而有所差异,因此在使用时需要参考特定系统的文档。

示例:

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>     // [posix] sleep()

// 信号处理函数
void signal_handler(int signum) {
    if (signum == SIGINT) {
        printf("Caught SIGINT signal, exiting...\n");
        exit(signum); // 退出程序
    }
}

int main() {
    // 注册SIGINT信号的处理函数
    if (signal(SIGINT, signal_handler) == SIG_ERR) {
        perror("signal");
        exit(EXIT_FAILURE);
    }

    printf("During the program's operation, you can press Ctrl+C to send a SIGINT signal.\n");
    while (1) {
        // 无限循环,直到接收到SIGINT信号
        sleep(1);
    }

    return 0;
}

stdlib

分类函数函数定义描述
内存管理mallocvoid *malloc(size_t size);分配指定大小的内存,并返回指向它的指针。
callocvoid *calloc(size_t num, size_t size);分配足够数量的内存块以容纳指定数量的对象,每个对象的大小由 size 指定,并初始化所有字节为 0。
reallocvoid *realloc(void *ptr, size_t newsize);更改之前分配的内存块的大小。
freevoid free(void *ptr);释放之前通过 malloccallocrealloc 分配的内存。
随机数生成randint rand(void);返回一个伪随机整数。
srandvoid srand(unsigned int seed);设置随机数生成器的种子。
字符串转换atofdouble atof(const char *nptr);将字符串转换为浮点数。
atoiint atoi(const char *nptr);将字符串转换为整数。
atollong int atol(const char *nptr);将字符串转换为长整数。
strtoddouble strtod(const char *nptr, char **endptr);将字符串转换为双精度浮点数,并返回转换后的值。
strtollong int strtol(const char *nptr, char **endptr, int base);将字符串转换为长整数,并返回转换后的值。
strtolllong long int strtoll(const char *nptr, char **endptr, int base);将字符串转换为长长整数,并返回转换后的值。
程序控制exitvoid exit(int status);终止程序执行,并返回一个状态码给操作系统。
abortvoid abort(void);导致程序异常终止。
systemint system(const char *string);执行一个 shell 命令,并返回命令的退出状态。
搜索和排序bsearchvoid *bsearch(const void *key, const void *base, size_t num, size_t size, int (*compar)(const void *, const void *));在数组中执行二分查找,并返回指向找到的元素的指针。
qsortvoid qsort(void *base, size_t num, size_t size, int (*compar)(const void *, const void *));对数组进行快速排序。

string

函数描述
strcpy(char *dest, const char *src)将字符串src复制到dest中。
strncpy(char *dest, const char *src, size_t n)将字符串src的前n个字符复制到dest中。
strcat(char *dest, const char *src)将字符串src追加到dest的末尾。
strncat(char *dest, const char *src, size_t n)将字符串src的前n个字符追加到dest的末尾。
strcmp(const char *s1, const char *s2)比较两个字符串s1s2
strncmp(const char *s1, const char *s2, size_t n)比较两个字符串s1s2的前n个字符。
strlen(const char *s)返回字符串s的长度(不包括结尾的空字符\0)。
strcspn(const char *s1, const char *s2)返回s1中第一个与s2中任何字符匹配的字符之前的字符数。
strspn(const char *s1, const char *s2)返回s1中第一个不在s2中的字符之前的字符数。
strchr(const char *s, int c)在字符串s中查找字符c的第一次出现。
strrchr(const char *s, int c)在字符串s中查找字符c的最后一次出现。
strstr(const char *s1, const char *s2)在字符串s1中查找子字符串s2的第一次出现。
memchr(const void *s, int c, size_t n)在内存块s的前n个字节中查找字符c的第一次出现。
memcmp(const void *s1, const void *s2, size_t n)比较内存块s1s2的前n个字节。
memcpy(void *dest, const void *src, size_t n)将内存块src的前n个字节复制到dest中。
memset(void *s, int c, size_t n)将内存块s的前n个字节设置为字符c
strtok(char *str, const char *delim)根据指定的分隔符delim来分割字符串str

需要注意的是,当使用这些函数时,需要确保字符串和内存块的大小足够,以避免缓冲区溢出等安全问题。

同时,对于某些函数(如strcpystrcat),建议使用更安全的版本(如strncpystrncat),以防止潜在的缓冲区溢出问题。

time

函数名称描述
time_t time(time_t *tloc)返回当前时间(自1970年1月1日以来的秒数),如果tloc不是NULL,则也将其设置为当前时间。
double difftime(time_t time2, time_t time1)返回time2和time1之间的时间差(以秒为单位)。
struct tm *localtime(const time_t *timer)将time_t类型的时间转换为本地时间,并返回一个指向tm结构体的指针。
struct tm *gmtime(const time_t *timer)将time_t类型的时间转换为UTC时间,并返回一个指向tm结构体的指针。
char *asctime(const struct tm *timeptr)将tm结构体转换为字符串形式的日期和时间。
char *ctime(const time_t *timer)将time_t类型的时间转换为本地时间,并以字符串形式返回。
size_t strftime(char *s, size_t maxsize, const char *format, const struct tm *timeptr)根据指定的格式,将tm结构体转换为字符串。
time_t mktime(struct tm *timeptr)将tm结构体转换为time_t类型的时间(秒数)。
clock_t clock(void)返回程序启动到调用时处理器所使用的时间(毫秒数)。

示例:

#include <stdint.h>
#include <stdio.h>
#include <time.h>

void delay_soft_ms(uint32_t ms)
{
    volatile uint32_t i = 0;
    while(ms--)
    {
        for(i = 0; i < 0x80000; i++)
        {
            ;
        }
    }
}

int main(int argc, char *argv[])
{
    time_t t0, t1;
    time_t t;
    struct tm *info;
    struct tm tminfo;
    time_t t2;
    char buf[80];
    clock_t clock_start, clock_end;
    double cpu_time_used = 0;

    printf("----- 当前时间 -----\n");
    t = time(NULL);
    printf("the count of timer is : %ld\n", (long)t);
    printf("ctime : %s", ctime(&t));

    printf("----- UTC时间和本地时间 -----\n");
    info = gmtime(&t);
    printf("gmtime : %s",asctime(info));
    info = localtime(&t);
    printf("localtime : %s",asctime(info));

    printf("----- 指定格式显示 -----\n");
    strftime(buf, 80, "%Y-%m-%d %H:%M:%S", info);  
    printf("Local date and time: %s\n", buf); 

    printf("----- 计算耗时 -----\n");
    time(&t0);
    delay_soft_ms(2000);
    time(&t1);
    printf("the difftime is %0.2f\n", difftime(t1,t0));

    printf("----- 手动配置时间 -----\n");
    tminfo.tm_year = 2023-1900;  // 年份是从1900年开始计算的  
    tminfo.tm_mon = 0;           // 月份是从0开始的  
    tminfo.tm_mday = 1;          // 一个月中的第几天  
    tminfo.tm_hour = 0;
    tminfo.tm_min = 0;
    tminfo.tm_sec = 0;
    tminfo.tm_isdst = -1;        // 让库函数自己去计算夏令时

    t2 = mktime(&tminfo);
    printf("The time represented by tminfo is %ld\n", (long)t2);
    printf("ctime : %s", ctime(&t2));

    printf("----- clock -----\n");
    clock_start = clock();
    delay_soft_ms(2000);
    clock_end = clock();
    cpu_time_used = ((double) (clock_end - clock_start)) / CLOCKS_PER_SEC;  
    printf("cpu time used: %.6f seconds\n", cpu_time_used);
    
    return 0;
}