title: 字符串处理与系统信息 tags: [
嵌入式Linux,
Linux应用编程,
字符串,
正则表达式,
时间,
proc文件系统,
内存分配,
IMX6ULL,
] created: 2026-09-18 updated: 2026-09-18
💡 关联知识:[[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 库函数;应用程序又经常需要获取系统信息并向操作系统申请资源(堆内存)。本篇把字符串处理与系统信息 / 系统资源自包含地讲清楚。
#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 / fgets 与 scanf("%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 字符(含空格/换行)"]
#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) 不带 \0;n 更大时补 \0 |
dest |
memset / bzero |
以字节填充 c / 清 0 |
s / 无 |
strcmp / strncmp |
按 ASCII 逐字符比较,最多 n 个 | <0 小于、0 相等、>0 大于 |
strchr / strrchr |
正/反向找字符,\0 也是字符串一部分 |
找到返回指针,否则 NULL |
strstr |
找子串首次出现位置 | 指针 / NULL |
sizeof 与 strlen:sizeof 是编译期操作符,算内存大小;strlen 是运行时库函数,算到 \0 的长度。对 char str[50]="abc"; char *p=str;,sizeof(str)=50、strlen(str)=3、sizeof(p)=8(64 位)、strlen(p)=3。函数参数中的数组会退化为指针。
memcpy / memmove / bcopy 也能拷贝(本质是内存拷贝):memcpy 要求源与目标不重叠,重叠用 memmove。
⚠️ 来源说明:本节不属于《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 */
#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 结束。strtol 的 endptr 若非 NULL 存第一个无效字符地址(无数字时存 nptr 并返回 0);base=0 时 0x 前缀为 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
int main(int argc, char **argv) { /* 或 char *argv[] */ }
argc 是参数个数(含程序自身路径);argv[0] 为程序路径,argv[1] 起为真实参数;空格分隔,带空格的参数用引号包裹。./app 112233 时 argc=2,argv[0]="./app",argv[1]="112233"。
⚠️ 来源说明:本节不属于《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;
}
}
正则表达式描述字符串匹配模式,由普通字符与元字符组成。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_EXTENDED;pmatch 中 rm_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(®, argv[1], REG_EXTENDED); /* 1. 编译 */
if (ret) { regerror(ret, ®, errbuf, sizeof(errbuf));
fprintf(stderr, "regcomp: %s\n", errbuf); return 1; }
p = argv[2]; length = strlen(argv[2]);
while (length > 0 && 0 == regexec(®, 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(®); /* 3. 释放 */
return 0;
}
#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 |
系统节拍率 |
基本概念:GMT 是格林威治标准时间(零时区),北京时间(东八区)比它早 8 小时;UTC 是世界协调时间,比 GMT 更精准,编程中不必刻意区分;全球 24 个时区,中国统一用北京时间。时区配置在 /usr/share/zoneinfo,系统本地时间由 /etc/localtime(通常软链接到其中某文件)定义。
系统时钟与 RTC:系统时钟由内核维护,开机时读取一次 RTC 作为初始值,关机不存在;RTC 有电池供电,关机仍计时,关机时内核把系统时钟写回 RTC。jiffies 是内核全局变量,记录自启动以来的节拍数;节拍率低(如 100Hz)精度低、中断负担小,默认 100Hz;time() / gettimeofday() 实质由 jiffies 换算。
#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; };
#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。
#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
进程时间 = 用户 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 不能区分用户/系统时间。
#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。
#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:提升可读性、可维护性,及时归还避免内存泄漏。
⚠️ 来源说明:本节不属于《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"]
/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;
}
⚠️ 来源说明:三个文件本身在教材 /proc 文件列表中已列出,字段含义解析为扩展知识。
/proc/cpuinfo:每逻辑 CPU 一段,字段 processor、model name、cpu MHz、flags 等。/proc/meminfo:MemTotal、MemFree、MemAvailable、Buffers、Cached、SwapTotal、SwapFree(单位 kB)。/proc/stat:cpu / cpu0… 各列依次 user、nice、system、idle、iowait、irq、softirq、steal;另有 intr、ctxt、btime、processes。常用于计算 CPU 利用率。#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(®, "[0-9]+", REG_EXTENDED);
if (ret) { regerror(ret, ®, err, sizeof(err)); printf("[regex] %s\n", err); return; }
printf("[regex] matches:");
while (0 == regexec(®, 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(®);
}
/* 系统信息 */
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_string 用 strncat/strncpy 演示长度受控操作(strncpy 在 n<=len 时不带 \0,需手工补),atoi/strtol/snprintf 做数字互转;demo_regex 用 regcomp 编译 [0-9]+、regexec 循环提取、regfree 释放;demo_sysinfo 依次调用 uname/gethostname/sysinfo/sysconf,内存字节数 = 字段 × mem_unit;demo_time 用 time+localtime_r/gmtime_r+strftime 与 gettimeofday;demo_resource 用 times/clock 测进程时间、srand+rand 取随机数、sleep 休眠、malloc/free 管理堆、open+read 读 /proc/version。
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 |
进制/前缀不匹配 | 检查 base;base=0 时 0x 才识别 |
regcomp 出错 |
正则语法错误 | regerror 打印描述;确认 REG_EXTENDED |
mktime 偏差一个月 |
tm_mon 未加 1 |
tm_mon 为 0~11 |
| 时间显示 1970 | struct tm 未初始化 |
先 localtime_r 再 strftime |
sysinfo 内存偏小 |
忘了乘 mem_unit |
字节数 = 字段 × mem_unit |
调试技巧:strace 看系统调用、ltrace 看库函数、valgrind --leak-check=full 查泄漏;正则先用 grep -E 验证语法。
| 维度 | 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 通用。
strcpy / strncpy / memcpy 有什么区别?各适用什么场景?答案:strcpy 复制以 \0 结尾的字符串并连 \0 一起复制,无长度限制;strncpy 最多复制 n 个字符,n <= strlen(src) 时不补 \0(需手工补),n 更大时用 \0 填充,适合固定长度字段;memcpy 是纯内存拷贝,不关心 \0,适合结构体/二进制数据,要求源与目标不重叠(重叠用 memmove)。关键:str* 以 \0 为界,mem* 以字节数为界。
gets 为什么被废弃?fgets 和 scanf("%s") 在读入换行符上有什么不同?答案:gets 不检查缓冲区大小,输入超长会栈溢出,C11 起被移除。fgets 最多读 size-1 字符,把换行符读走并保留在字符串中,末尾补 \0(可用 s[strcspn(s,"\n")]='\0'; 去掉换行);scanf("%s") 遇空白停止且不取走分隔符,分隔符残留在缓冲区影响后续读取,也无法直接限长(可用 %ns)。读一行用 fgets,读单词可用 scanf("%s")。
sizeof 和 strlen 有什么区别?char str[50]="abc"; char *p=str; 结果分别是多少?答案:sizeof 是编译期操作符,算内存大小;strlen 是运行时库函数,算到 \0 的长度。sizeof(str)=50、strlen(str)=3、sizeof(p)=8(64 位)、strlen(p)=3。陷阱:函数参数中的数组退化为指针,sizeof 得到指针大小;strlen 要求以 \0 结尾,否则越界读取。
regcomp / regexec 的匹配流程是什么?rm_so / rm_eo 如何理解?答案:三步——regcomp 编译正则(失败用 regerror 取描述);regexec 执行匹配(0 匹配、REG_NOMATCH 不匹配);regfree 释放。rm_so 是匹配子串相对字符串起始的起始偏移,rm_eo 是结束偏移(左闭右开),子串长度 rm_eo - rm_so,内容 str + rm_so,rm_so == -1 表示未匹配。循环匹配时每次把指针前移 rm_eo 即可逐个提取。
malloc / calloc 有什么区别?进程退出时内核会回收内存,为什么还要 free?sysinfo 内存字段如何换算成字节?答案:malloc(size) 分配且不初始化,内容为垃圾数据;calloc(nmemb,size) 分配 nmemb*size 字节并全部置 0。仍需 free:显式释放提升可读性、可维护性;程序生命周期中内存常是阶段性需要,不及时释放会造成内存泄漏,常驻服务会持续吃内存直至 OOM。sysinfo 的 totalram/freeram 是内存单元数,实际字节数 = 字段 × mem_unit。
内容来源:《I.MX6U嵌入式Linux C应用编程指南》第六章 字符串处理、第七章 系统信息与系统资源