05-字符串处理与系统信息.md 34 KB


title: 字符串处理与系统信息 tags: [

嵌入式Linux,
Linux应用编程,
字符串,
正则表达式,
时间,
proc文件系统,
内存分配,
IMX6ULL,

] created: 2026-09-18 updated: 2026-09-18

pdf_ref: "《I.MX6U嵌入式Linux C应用编程指南V1.6》第六章 字符串处理、第七章 系统信息与系统资源"

字符串处理与系统信息

💡 关联知识:[[01-Linux应用编程基础/03-标准IO库]]、[[01-Linux应用编程基础/04-文件属性与目录]]、[[01-Linux应用编程基础/面试-Linux应用编程基础]];延伸阅读:[[Linux+C+C++技术体系梳理/2. Linux系统编程/1. C库文件IO]]、[[Linux+C+C++技术体系梳理/1. C语言基础/20. 字符串函数]]、[[Linux+C+C++技术体系梳理/1. C语言基础/21. 内存管理函数]]

C 语言没有内建字符串类型,字符串处理依赖 C 库函数;应用程序又经常需要获取系统信息并向操作系统申请资源(堆内存)。本篇把字符串处理系统信息 / 系统资源自包含地讲清楚。


1. 字符串输入 / 输出

#include <stdio.h>
int puts(const char *s);                 /* 输出字符串到 stdout,自动换行 */
int putchar(int c);                      /* 输出一个字符到 stdout */
int fputc(int c, FILE *stream);          /* 输出字符到指定流 */
int fputs(const char *s, FILE *stream);  /* 输出字符串到指定流 */
char *gets(char *s);                     /* 从 stdin 读一行(不安全,已废弃) */
int getchar(void);                       /* 从 stdin 读一个字符 */
int fgetc(FILE *stream);                 /* 从指定流读一个字符 */
char *fgets(char *s, int size, FILE *stream); /* 从指定流读一行,限制长度 */
函数 目标/来源 换行符处理 返回值
puts stdout 自动追加 \n\0\n 非负数 / EOF
putchar stdout 出错 EOF
fputc / fputs 指定 stream 成功返回字符/非负数,失败 EOF
gets stdin 读走并丢弃 \n,替换为 \0 s / NULL
getchar / fgetc stdin / 指定流 作为普通字符读取 字符(int) / EOF
fgets 指定流 读走 \n 并保留,末尾补 \0 s / NULL

printf 是格式化输出,功能更强;puts 等更简单且同样使用 stdio 缓冲。gets 不检查缓冲区溢出,编译告警,应改用 fgets

gets / fgetsscanf("%s") 的区别

  • gets / fgets 允许空格、TAB,仅换行符作分隔;scanf("%s") 把空格、TAB、换行都当分隔符,一个 %s 只读一个词。
  • gets 取走并丢弃换行符;scanf 不取走分隔符,残留在缓冲区影响下一次读取;scanf("%c") 会把空格、TAB、换行当正常字符读入。

    graph TD
    BUF["输入缓冲区"] --> G["gets: 读整行, 丢弃 \\n"]
    BUF --> FG["fgets: 读整行(限长), 保留 \\n"]
    BUF --> SC["scanf(%s): 遇分隔符停止, 保留分隔符"]
    BUF --> GC["getchar/fgetc: 读 1 字符(含空格/换行)"]
    

2. 字符串长度、拼接、拷贝、填充、比较、查找

#include <string.h>
size_t strlen(const char *s);                                    /* 长度,不含 '\0' */
char *strcat(char *dest, const char *src);                       /* 拼接 */
char *strncat(char *dest, const char *src, size_t n);            /* 拼接最多 n 字符 */
char *strcpy(char *dest, const char *src);                       /* 拷贝(含 '\0') */
char *strncpy(char *dest, const char *src, size_t n);            /* 拷贝最多 n 字符 */
void *memset(void *s, int c, size_t n);                          /* 内存填充 */
int strcmp(const char *s1, const char *s2);                      /* 比较 */
int strncmp(const char *s1, const char *s2, size_t n);           /* 比较前 n 字符 */
char *strchr(const char *s, int c);                              /* 正向查找字符 */
char *strrchr(const char *s, int c);                             /* 反向查找字符 */
char *strstr(const char *haystack, const char *needle);          /* 查找子串 */

#include <strings.h>
void bzero(void *s, size_t n);                                   /* 内存清 0 */
函数 要点 返回值
strlen 参数须以 \0 结尾,不含结束符 字节长度
strcat 覆盖 dest\0,只留一个 \0,须保证空间足够 dest
strncat 最多追加 n 字符;src 足够长时追加 n+1 字节 dest
strcpy \0 一起复制,须保证空间 dest
strncpy n <= strlen(src) 不带 \0n 更大时补 \0 dest
memset / bzero 以字节填充 c / 清 0 s / 无
strcmp / strncmp 按 ASCII 逐字符比较,最多 n 个 <0 小于、0 相等、>0 大于
strchr / strrchr 正/反向找字符,\0 也是字符串一部分 找到返回指针,否则 NULL
strstr 找子串首次出现位置 指针 / NULL

sizeofstrlensizeof 是编译期操作符,算内存大小;strlen 是运行时库函数,算到 \0 的长度。对 char str[50]="abc"; char *p=str;sizeof(str)=50strlen(str)=3sizeof(p)=8(64 位)、strlen(p)=3。函数参数中的数组会退化为指针。

memcpy / memmove / bcopy 也能拷贝(本质是内存拷贝):memcpy 要求源与目标不重叠,重叠用 memmove

2.1 strtok 字符串分割(扩展)

⚠️ 来源说明:本节不属于《I.MX6U嵌入式Linux C应用编程指南》内容,为扩展知识。

#include <string.h>
char *strtok(char *str, const char *delim);

第一次传待分割字符串,之后传 NULL;按 delim 中任意字符分割。返回每个子串,无可分割时返回 NULL。会修改原字符串(分隔符改写为 \0),非线程安全,多线程用 strtok_r

char buf[] = "192.168.1.100";
char *p = strtok(buf, ".");
while (NULL != p) { printf("%s\n", p); p = strtok(NULL, "."); } /* 192 168 1 100 */

3. 字符串与数字互转

#include <stdlib.h>
int atoi(const char *nptr);            long atol(const char *nptr);       long long atoll(const char *nptr);
long int strtol(const char *nptr, char **endptr, int base);
long long int strtoll(const char *nptr, char **endptr, int base);
unsigned long int strtoul(const char *nptr, char **endptr, int base);
unsigned long long int strtoull(const char *nptr, char **endptr, int base);
double atof(const char *nptr);
double strtod(const char *nptr, char **endptr);
float strtof(const char *nptr, char **endptr);
long double strtold(const char *nptr, char **endptr);
函数 返回类型 进制支持
atoi / atol / atoll int / long / long long 仅十进制
strtol / strtoll long / long long base 2~36,或 0 自动识别
strtoul / strtoull unsigned long / unsigned long long 同上
atof / strtod / strtof / strtold double / double / float / long double 浮点

atoi 系列跳过前导空格,遇数字或正负号开始,遇非数字或 \0 结束。strtolendptr 若非 NULL 存第一个无效字符地址(无数字时存 nptr 并返回 0);base=00x 前缀为 16 进制、0 前缀为 8 进制。

数字转字符串:用格式化 I/O,sprintf / snprintf 输出到缓冲区:

char str[20];
sprintf(str, "%d", 500);      /* "500" */
sprintf(str, "%f", 500.111);  /* "500.111000" */
snprintf(str, sizeof(str), "%u", 500);  /* 推荐,防溢出 */
graph LR
    S["字符串 \"0x1F4\""] -->|strtol base=16| L["long 500"]
    S -->|atof/strtod| D["double"]
    L -->|snprintf| S2["字符串 \"500\""]
    D -->|sprintf %f| S2

4. 给应用程序传参

int main(int argc, char **argv) { /* 或 char *argv[] */ }

argc 是参数个数(含程序自身路径);argv[0] 为程序路径,argv[1] 起为真实参数;空格分隔,带空格的参数用引号包裹。./app 112233argc=2argv[0]="./app"argv[1]="112233"

4.1 getopt 选项解析(扩展)

⚠️ 来源说明:本节不属于《I.MX6U嵌入式Linux C应用编程指南》内容,为扩展知识。

#include <unistd.h>
int getopt(int argc, char * const argv[], const char *optstring);
extern char *optarg;   /* 当前选项的参数 */
extern int optind;     /* 下一个待处理参数的索引 */
extern int optopt;     /* 未识别的选项字符 */

optstring 中字符后带 : 表示该选项需要参数,参数放入 optarg;解析完返回 -1

int opt;
while ((opt = getopt(argc, argv, "f:n:")) != -1) {
    switch (opt) {
    case 'f': printf("file = %s\n", optarg); break;
    case 'n': printf("num  = %s\n", optarg); break;
    }
}

5. 正则表达式

正则表达式描述字符串匹配模式,由普通字符元字符组成。POSIX 场景流程:编译 → 匹配 → 释放。

#include <sys/types.h>
#include <regex.h>
int regcomp(regex_t *preg, const char *regex, int cflags);
int regexec(const regex_t *preg, const char *string, size_t nmatch,
            regmatch_t pmatch[], int eflags);
size_t regerror(int errcode, const regex_t *preg, char *errbuf, size_t errbuf_size);
void regfree(regex_t *preg);

typedef struct { regoff_t rm_so; regoff_t rm_eo; } regmatch_t; /* rm_so 起始, rm_eo 结束 */
函数 作用 返回值
regcomp 把正则编译为 regex_t 成功 0,失败返回非 0 错误码
regexec 执行匹配,结果写入 pmatch 匹配 0;REG_NOMATCH 未匹配;其它为错误
regerror 错误码转描述字符串 字符串长度
regfree 释放资源

cflags 常用 REG_EXTENDEDpmatchrm_so 为起始偏移、rm_eo 为结束偏移(左闭右开),rm_so == -1 表示未匹配;eflags 常传 0。子串内容 string + rm_so,长度 rm_eo - rm_so

元字符 含义 元字符 含义
. 任意单字符 * 前一项 0 次或多次
^ / $ 行首 / 行尾 + 前一项 1 次或多次(扩展)
[...] 字符集合 ? 前一项 0 次或 1 次(扩展)
[^...] 否定字符集 {n,m} 前一项 n~m 次(扩展)
\ 转义 \| / () 或 / 分组(扩展)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <regex.h>

int main(int argc, char *argv[])
{
    regex_t reg; regmatch_t pmatch; char errbuf[64]; int ret;
    const char *p; int length;

    if (3 != argc) { fprintf(stderr, "usage: %s <regex> <string>\n", argv[0]); return 1; }
    ret = regcomp(&reg, argv[1], REG_EXTENDED);       /* 1. 编译 */
    if (ret) { regerror(ret, &reg, errbuf, sizeof(errbuf));
               fprintf(stderr, "regcomp: %s\n", errbuf); return 1; }
    p = argv[2]; length = strlen(argv[2]);
    while (length > 0 && 0 == regexec(&reg, p, 1, &pmatch, 0)) {  /* 2. 逐个匹配 */
        if (pmatch.rm_so == pmatch.rm_eo) { p++; length--; continue; } /* 空匹配 */
        printf("%.*s\n", (int)(pmatch.rm_eo - pmatch.rm_so), p + pmatch.rm_so);
        p += pmatch.rm_eo; length -= pmatch.rm_eo;
    }
    regfree(&reg);                                     /* 3. 释放 */
    return 0;
}

6. 系统信息

#include <sys/utsname.h>   int uname(struct utsname *buf);
#include <sys/sysinfo.h>   int sysinfo(struct sysinfo *info);
#include <unistd.h>        int gethostname(char *name, size_t len);
long sysconf(int name);
struct utsname {            /* 成员均为字符数组 */
    char sysname[];   /* 操作系统名称 */
    char nodename[];  /* 主机名 */
    char release[];   /* 内核版本 */
    char version[];   /* 发行版本 */
    char machine[];   /* 硬件架构 */
#ifdef _GNU_SOURCE
    char domainname[];
#endif
};
struct sysinfo {
    long uptime;            /* 启动后经过时间(秒) */
    unsigned long loads[3]; /* 1,5,15 分钟负载 */
    unsigned long totalram; /* 总可用内存 */
    unsigned long freeram;  /* 未使用内存 */
    unsigned long sharedram; unsigned long bufferram;
    unsigned long totalswap; unsigned long freeswap;
    unsigned short procs;   /* 当前进程数 */
    unsigned long totalhigh; unsigned long freehigh;
    unsigned int mem_unit;  /* 内存单元大小(字节) */
    char _f[20-2*sizeof(long)-sizeof(int)]; /* Padding to 64 bytes */
};

uname / sysinfo / gethostname 均成功返回 0、失败返回 -1 并设置 errno。内存实际字节数 = 字段值 × mem_unit

sysconf 运行时获取系统配置,成功返回配置值,参数无效返回 -1 并 errno=EINVAL

参数 含义 参数 含义
_SC_ARG_MAX exec 参数最大长度 _SC_OPEN_MAX 进程可打开最大文件数
_SC_CHILD_MAX 每用户最大并发进程数 _SC_PAGESIZE 系统页大小
_SC_HOST_NAME_MAX 主机名最大长度 _SC_TTY_NAME_MAX 终端名最大长度
_SC_LOGIN_NAME_MAX 登录名最大长度 _SC_CLK_TCK 系统节拍率

7. 时间与日期

基本概念:GMT 是格林威治标准时间(零时区),北京时间(东八区)比它早 8 小时;UTC 是世界协调时间,比 GMT 更精准,编程中不必刻意区分;全球 24 个时区,中国统一用北京时间。时区配置在 /usr/share/zoneinfo,系统本地时间由 /etc/localtime(通常软链接到其中某文件)定义。

系统时钟与 RTC:系统时钟由内核维护,开机时读取一次 RTC 作为初始值,关机不存在;RTC 有电池供电,关机仍计时,关机时内核把系统时钟写回 RTC。jiffies 是内核全局变量,记录自启动以来的节拍数;节拍率低(如 100Hz)精度低、中断负担小,默认 100Hz;time() / gettimeofday() 实质由 jiffies 换算。

7.1 获取时间

#include <time.h>
time_t time(time_t *tloc);            /* 秒级,返回自 1970-01-01 00:00:00 UTC 起的秒数 */
#include <sys/time.h>
int gettimeofday(struct timeval *tv, struct timezone *tz);  /* 微秒级,tz 已废弃传 NULL */
struct timeval { long tv_sec; long tv_usec; };

7.2 时间转换

#include <time.h>
char *ctime(const time_t *timep);                          char *ctime_r(const time_t *, char *);
struct tm *localtime(const time_t *timep);                 struct tm *localtime_r(const time_t *, struct tm *);
struct tm *gmtime(const time_t *timep);                    struct tm *gmtime_r(const time_t *, struct tm *);
time_t mktime(struct tm *tm);
char *asctime(const struct tm *tm);                        char *asctime_r(const struct tm *, char *);
size_t strftime(char *s, size_t max, const char *format, const struct tm *tm);

struct tm {
    int tm_sec;   /* 秒(0-60) */          int tm_mon;   /* 月(0-11) */
    int tm_min;   /* 分(0-59) */          int tm_year;  /* 年(自 1900 起的年数) */
    int tm_hour;  /* 时(0-23) */          int tm_wday;  /* 星期(0-6, 日=0) */
    int tm_mday;  /* 日(1-31) */          int tm_yday;  /* 年内第几天(0-365) */
    int tm_isdst; /* 夏令时 */
};
函数 作用 可重入版本
ctime time_t → 本地时间字符串 ctime_r
localtime time_t → struct tm(本地时间) localtime_r
gmtime time_t → struct tm(UTC 时间) gmtime_r
mktime struct tm → time_t(与 localtime 相反)
asctime struct tm → 固定格式字符串 asctime_r
strftime struct tm → 自定义格式字符串

注意 tm_year 需加 1900、tm_mon 需加 1。 strftime 常用说明符:%Y 4 位年、%y 2 位年、%m 月、%d 日、%H 24 时、%I 12 时、%M 分、%S 秒、%A 完整星期、%a 星期缩写、%B 完整月份、%b 月份缩写、%p AM/PM、%j 年内第几天、%F=%Y-%m-%d%T=%H:%M:%S。返回值:不超 max 返回写入字节数,否则返回 0。

7.3 设置时间

#include <sys/time.h>
int settimeofday(const struct timeval *tv, const struct timezone *tz);  /* 需 root,tz 传 NULL */
graph LR
    T["time_t"] -->|localtime_r| TM["struct tm 本地"]
    T -->|gmtime_r| UTC["struct tm UTC"]
    T -->|ctime_r| CS["固定格式字符串"]
    TM -->|mktime| T
    TM -->|strftime| FS["自定义格式字符串"]
    TM -->|asctime_r| AS["固定格式字符串"]
    TV["struct timeval 秒+微秒"] -->|gettimeofday| T

8. 进程时间

进程时间 = 用户 CPU 时间 + 系统 CPU 时间;休眠不消耗 CPU,不计入。注意区分「时间总和」(生命周期,含休眠)与「进程时间」。

#include <sys/times.h>
clock_t times(struct tms *buf);
struct tms {
    clock_t tms_utime;  /* 用户 CPU 时间(节拍数) */   clock_t tms_cutime; /* 已死子进程用户时间总和 */
    clock_t tms_stime;  /* 系统 CPU 时间(节拍数) */   clock_t tms_cstime; /* 已死子进程系统时间总和 */
};
#include <time.h>
clock_t clock(void);    /* 总 CPU 时间, 除以 CLOCKS_PER_SEC 得秒; 不可用返回 -1 */

times 返回自过去某时间点起的节拍数,节拍数 / 节拍率(_SC_CLK_TCK) 即秒数,失败 -1。clock 不能区分用户/系统时间。


9. 随机数与休眠

#include <stdlib.h>
int rand(void);                     /* 返回 [0, RAND_MAX] 伪随机数 */
void srand(unsigned int seed);      /* 设置种子, 常用 srand(time(NULL)) */

#include <unistd.h>
unsigned int sleep(unsigned int seconds);   /* 秒级; 被信号中断返回剩余秒数 */
int usleep(useconds_t usec);                /* 微秒级; 成功 0, 失败 -1 */
#include <time.h>
int nanosleep(const struct timespec *req, struct timespec *rem); /* 纳秒级, 系统调用 */

未调用 srand 时默认种子为 1,每次运行序列相同;srand(time(NULL)) 使序列每次不同。编程中只能得到伪随机数。休眠时进程主动交出 CPU、退出调度队列,是延时而非裸机 nop 空转;nanosleep 被中断返回 -1,剩余时间写入 rem


10. 申请堆内存

#include <stdlib.h>
void *malloc(size_t size);              /* 分配 size 字节, 不初始化; 失败或 size=0 返回 NULL */
void free(void *ptr);                   /* 释放 */
void *calloc(size_t nmemb, size_t size);/* 分配 nmemb*size 字节并全部置 0 */
int posix_memalign(void **memptr, size_t alignment, size_t size);
void *aligned_alloc(size_t alignment, size_t size);
void *valloc(size_t size);
#include <malloc.h>
void *memalign(size_t alignment, size_t size);
void *pvalloc(size_t size);
函数 对齐要求 返回
malloc / calloc 固定较小边界(32 位 8 字节,64 位 16 字节) 指针 / NULL
posix_memalign alignment 为 2 的幂且是 sizeof(void*) 倍数 成功 0,失败非 0
aligned_alloc alignment 为 2 的幂,size 为其整数倍 指针 / NULL
memalign / valloc aligned_alloc / 页对齐(均已过时) 指针 / NULL

calloc 等价于 malloc + memset。进程终止时内核回收全部内存,但仍应显式 free:提升可读性、可维护性,及时归还避免内存泄漏。

10.1 brk / sbrk(扩展)

⚠️ 来源说明:本节不属于《I.MX6U嵌入式Linux C应用编程指南》内容,为扩展知识。

#include <unistd.h>
int brk(void *addr);              /* 直接设置堆顶, 成功 0, 失败 -1(ENOMEM) */
void *sbrk(intptr_t increment);   /* 相对移动堆顶, 返回旧堆顶; 失败返回 (void*)-1 */

malloc 内部通常基于 brk / sbrk,实际编程推荐用 malloc / free

graph TD
    S["堆内存申请"] --> M["malloc: 不初始化"]
    S --> C["calloc: 置 0"]
    S --> A["对齐分配: posix_memalign / aligned_alloc / memalign / valloc"]
    M --> F["free: 显式释放"]
    C --> F
    A --> F
    M -. 内部 .-> B["brk / sbrk"]

11. proc 文件系统

/proc虚拟文件系统,以文件方式向应用层暴露内核数据;动态创建、只存在于内存、不占磁盘(与 devfs 类似)。以 PID 命名的目录记录进程信息;虚拟文件含义:

文件 含义 文件 含义
cmdline 内核启动参数 meminfo 物理内存与交换分区使用情况
cpuinfo CPU 相关信息 modules 加载的模块列表
iomem IO 设备内存使用 mounts 挂载的文件系统列表
interrupts 被占用的中断及占用者 partitions 系统识别的分区表
ioports IO 端口使用 swaps 交换分区利用情况
kcore 物理内存映像,不可读 version 内核版本信息
loadavg 系统平均负载 uptime 系统运行时间

读取方式:cat /proc/version,或程序中 open + read

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
int main(void) {
    char buf[512] = {0}; int fd = open("/proc/version", O_RDONLY);
    if (-1 == fd) { perror("open error"); exit(-1); }
    if (-1 == read(fd, buf, sizeof(buf))) { perror("read error"); exit(-1); }
    puts(buf); close(fd); return 0;
}

11.1 cpuinfo / meminfo / stat 字段(扩展)

⚠️ 来源说明:三个文件本身在教材 /proc 文件列表中已列出,字段含义解析为扩展知识。

  • /proc/cpuinfo:每逻辑 CPU 一段,字段 processormodel namecpu MHzflags 等。
  • /proc/meminfoMemTotalMemFreeMemAvailableBuffersCachedSwapTotalSwapFree(单位 kB)。
  • /proc/statcpu / cpu0… 各列依次 user、nice、system、idle、iowait、irq、softirq、steal;另有 intrctxtbtimeprocesses。常用于计算 CPU 利用率。

12. 完整可复制源码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/times.h>
#include <sys/utsname.h>
#include <sys/sysinfo.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <regex.h>

/* 字符串与数字转换 */
static void demo_string(void)
{
    char dest[128] = "Linux "; char src[] = "string demo"; char num[32];
    strncat(dest, src, 7);                       /* 拼接前 7 字符 */
    printf("[str] strncat : %s\n", dest);
    strncpy(dest, "copy me", 7); dest[7] = '\0'; /* n<=len 需手工补 '\0' */
    printf("[str] strncpy : %s\n", dest);
    printf("[str] strcmp  : %d\n", strcmp("ABC", "abc"));
    printf("[str] strstr  : %s\n", strstr("I love my home", "home"));
    snprintf(num, sizeof(num), "%d", atoi("500") * 2);
    printf("[str] atoi/snprintf : %s\n", num);
    printf("[str] strtol  : %ld\n", strtol("0x500", NULL, 16));
    printf("[str] atof    : %lf\n", atof("100.0123"));
}

/* 正则: 提取所有数字 */
static void demo_regex(void)
{
    regex_t reg; regmatch_t pm; char err[64];
    const char *p = "abc 123 def 456"; int ret;
    ret = regcomp(&reg, "[0-9]+", REG_EXTENDED);
    if (ret) { regerror(ret, &reg, err, sizeof(err)); printf("[regex] %s\n", err); return; }
    printf("[regex] matches:");
    while (0 == regexec(&reg, p, 1, &pm, 0) && -1 != pm.rm_so) {
        printf(" %.*s", (int)(pm.rm_eo - pm.rm_so), p + pm.rm_so);
        p += pm.rm_eo;
    }
    printf("\n"); regfree(&reg);
}

/* 系统信息 */
static void demo_sysinfo(void)
{
    struct utsname os; struct sysinfo si; char host[64];
    if (0 == uname(&os)) printf("[sys] %s %s %s %s\n", os.sysname, os.release, os.machine, os.nodename);
    memset(host, 0, sizeof(host));
    if (0 == gethostname(host, sizeof(host))) printf("[sys] hostname = %s\n", host);
    if (0 == sysinfo(&si)) {
        printf("[sys] uptime=%lds totalram=%lu(x%d) freeram=%lu procs=%u\n",
               si.uptime, si.totalram, si.mem_unit, si.freeram, si.procs);
    }
    printf("[sys] pagesize=%ld clk_tck=%ld open_max=%ld\n",
           sysconf(_SC_PAGESIZE), sysconf(_SC_CLK_TCK), sysconf(_SC_OPEN_MAX));
}

/* 时间 */
static void demo_time(void)
{
    time_t sec = time(NULL); struct tm t; char s[100]; struct timeval tv;
    if ((time_t)-1 == sec) return;
    localtime_r(&sec, &t); strftime(s, sizeof(s), "%Y-%m-%d %H:%M:%S", &t);
    printf("[time] local = %s\n", s);
    gmtime_r(&sec, &t); strftime(s, sizeof(s), "%Y-%m-%d %H:%M:%S", &t);
    printf("[time] utc   = %s\n", s);
    if (0 == gettimeofday(&tv, NULL)) printf("[time] %ld s + %ld us\n", tv.tv_sec, tv.tv_usec);
}

/* 进程时间 / 随机数 / 休眠 / 堆内存 / proc */
static void demo_resource(void)
{
    struct tms tb; long tck = sysconf(_SC_CLK_TCK); char *mem; int fd; char pb[256];
    if ((clock_t)-1 != times(&tb)) printf("[res] user cpu = %.4f s\n", tb.tms_utime / (double)tck);
    printf("[res] clock() = %.4f s\n", clock() / (double)CLOCKS_PER_SEC);
    srand(time(NULL));
    printf("[res] rand = %d %d %d\n", rand() % 100, rand() % 100, rand() % 100);
    puts("[res] sleep 1s ..."); sleep(1);
    mem = (char *)malloc(1024);
    if (mem) { memset(mem, 0, 1024); printf("[res] malloc 1024 at %p\n", (void *)mem); free(mem); }
    fd = open("/proc/version", O_RDONLY);
    if (fd >= 0) { memset(pb, 0, sizeof(pb)); if (read(fd, pb, sizeof(pb) - 1) > 0) printf("[res] %s", pb); close(fd); }
}

int main(int argc, char *argv[])
{
    int i;
    printf("argc = %d\n", argc);
    for (i = 0; i < argc; i++) printf("  argv[%d] = %s\n", i, argv[i]);
    demo_string(); demo_regex(); demo_sysinfo(); demo_time(); demo_resource();
    puts("done."); return 0;
}

逐段解释demo_stringstrncat/strncpy 演示长度受控操作(strncpyn<=len 时不带 \0,需手工补),atoi/strtol/snprintf 做数字互转;demo_regexregcomp 编译 [0-9]+regexec 循环提取、regfree 释放;demo_sysinfo 依次调用 uname/gethostname/sysinfo/sysconf,内存字节数 = 字段 × mem_unitdemo_timetime+localtime_r/gmtime_r+strftimegettimeofdaydemo_resourcetimes/clock 测进程时间、srand+rand 取随机数、sleep 休眠、malloc/free 管理堆、open+read/proc/version


13. 实验步骤与调试方法

gcc -Wall -o sysinfo_demo sysinfo_demo.c
./sysinfo_demo hello world

交叉验证:

uname -a; hostname; date; date -u; free -b; getconf PAGE_SIZE; getconf CLK_TCK
cat /proc/version; head -20 /proc/cpuinfo; head -20 /proc/meminfo

常见问题:

现象 原因 处理
strlen 异常大 字符串没有 \0 确保以 \0 结尾;容量用 sizeof
strcpy 段错误 目标缓冲区太小 改用 strncpy/snprintf
strtol 返回 0 进制/前缀不匹配 检查 basebase=00x 才识别
regcomp 出错 正则语法错误 regerror 打印描述;确认 REG_EXTENDED
mktime 偏差一个月 tm_mon 未加 1 tm_mon 为 0~11
时间显示 1970 struct tm 未初始化 localtime_rstrftime
sysinfo 内存偏小 忘了乘 mem_unit 字节数 = 字段 × mem_unit

调试技巧:strace 看系统调用、ltrace 看库函数、valgrind --leak-check=full 查泄漏;正则先用 grep -E 验证语法。


14. 跨平台对比:IMX6ULL vs STM32 vs RK3568

维度 I.MX6ULL(本教程) STM32(裸机/HAL) RK3568(Linux)
字符串库 glibc 完整 newlib 子集 glibc 完整
正则 POSIX regex.h 通常无,需第三方 POSIX regex.h
系统信息 uname/sysinfo/sysconf 直接读寄存器/UID 同 Linux
时间 time/gettimeofday + RTC SysTick + RTC,无时区 同 Linux,常带 NTP
进程时间 times/clock DWT 周期计数器 同 Linux
休眠 sleep/usleep/nanosleep HAL_Delay 忙等 同 Linux
堆内存 malloc/free newlib 堆,需配 heap 同 Linux
/proc 有,丰富

要点:STM32 无 /proc、无 POSIX 正则、无时区/日历时间体系;IMX6ULL 与 RK3568 同属 Linux,本篇 API 通用。


15. 面试精选(5 题)

Q1:strcpy / strncpy / memcpy 有什么区别?各适用什么场景?

答案strcpy 复制以 \0 结尾的字符串并连 \0 一起复制,无长度限制;strncpy 最多复制 n 个字符,n <= strlen(src) 时不补 \0(需手工补),n 更大时用 \0 填充,适合固定长度字段;memcpy 是纯内存拷贝,不关心 \0,适合结构体/二进制数据,要求源与目标不重叠(重叠用 memmove)。关键:str*\0 为界,mem* 以字节数为界。

Q2:gets 为什么被废弃?fgetsscanf("%s") 在读入换行符上有什么不同?

答案gets 不检查缓冲区大小,输入超长会栈溢出,C11 起被移除。fgets 最多读 size-1 字符,把换行符读走并保留在字符串中,末尾补 \0(可用 s[strcspn(s,"\n")]='\0'; 去掉换行);scanf("%s") 遇空白停止且不取走分隔符,分隔符残留在缓冲区影响后续读取,也无法直接限长(可用 %ns)。读一行用 fgets,读单词可用 scanf("%s")

Q3:sizeofstrlen 有什么区别?char str[50]="abc"; char *p=str; 结果分别是多少?

答案sizeof 是编译期操作符,算内存大小;strlen 是运行时库函数,算到 \0 的长度。sizeof(str)=50strlen(str)=3sizeof(p)=8(64 位)、strlen(p)=3。陷阱:函数参数中的数组退化为指针,sizeof 得到指针大小;strlen 要求以 \0 结尾,否则越界读取。

Q4:regcomp / regexec 的匹配流程是什么?rm_so / rm_eo 如何理解?

答案:三步——regcomp 编译正则(失败用 regerror 取描述);regexec 执行匹配(0 匹配、REG_NOMATCH 不匹配);regfree 释放。rm_so 是匹配子串相对字符串起始的起始偏移,rm_eo 是结束偏移(左闭右开),子串长度 rm_eo - rm_so,内容 str + rm_sorm_so == -1 表示未匹配。循环匹配时每次把指针前移 rm_eo 即可逐个提取。

Q5:malloc / calloc 有什么区别?进程退出时内核会回收内存,为什么还要 freesysinfo 内存字段如何换算成字节?

答案malloc(size) 分配且不初始化,内容为垃圾数据;calloc(nmemb,size) 分配 nmemb*size 字节并全部置 0。仍需 free:显式释放提升可读性、可维护性;程序生命周期中内存常是阶段性需要,不及时释放会造成内存泄漏,常驻服务会持续吃内存直至 OOM。sysinfototalram/freeram 是内存单元数,实际字节数 = 字段 × mem_unit


内容来源:《I.MX6U嵌入式Linux C应用编程指南》第六章 字符串处理、第七章 系统信息与系统资源