- C++
scanf 函数基础
- 2025-5-26 21:11:10 @
1. scanf 函数基础
1.1 函数原型与头文件
scanf
函数是 C 语言中用于格式化输入的标准函数,其函数原型定义在头文件 <stdio.h>
中。具体原型如下:
int scanf(const char *format, ...);
const char *format
:格式控制字符串,用于指定输入的格式。它由普通字符和格式说明符组成。普通字符用于匹配输入中的相同字符,而格式说明符则用于指定后续参数的输入格式。...
:表示可变参数列表,这些参数是用于存储输入数据的变量的地址。每个格式说明符对应一个参数,参数的类型必须与格式说明符匹配。
在使用 scanf
函数时,必须包含头文件 <stdio.h>
,否则编译器将无法识别该函数。
1.2 格式化输入的基本概念
scanf
函数通过格式控制字符串来实现灵活的输入。格式控制字符串中的格式说明符以 %
开头,后跟一个或多个字符,用于指定输入数据的类型和格式。以下是一些常见的格式说明符及其含义:
格式说明符 | 含义 |
---|---|
%d 或 %i |
输入有符号十进制整数。例如:scanf("%d", &var); 输入 123 ,var 的值为 123 。 |
%u |
输入无符号十进制整数。例如:scanf("%u", &var); 输入 456 ,var 的值为 456 。 |
%f |
输入浮点数。例如:scanf("%f", &var); 输入 3.14 ,var 的值为 3.14 。 |
%c |
输入单个字符。例如:scanf("%c", &var); 输入 A ,var 的值为 A 。 |
%s |
输入字符串。例如:scanf("%s", str); 输入 Hello ,str 的值为 "Hello" 。 |
%p |
输入指针地址。例如:scanf("%p", &ptr); 输入地址,ptr 的值为该地址。 |
%% |
输入一个百分号 % 。例如:scanf("%%", &var); 输入 % ,var 的值为 % 。 |
格式说明符还可以包含一些可选的修饰符,用于控制输入的宽度、精度、对齐方式等。以下是一些常见的修饰符及其用法:
-
宽度修饰符:指定输入的最大宽度。对于字符串,指定最大输入字符数。例如:
char str[10]; scanf("%9s", str); // 最多读取 9 个字符
-
精度修饰符:对于浮点数,指定小数点后的位数。例如:
float var; scanf("%f", &var); // 默认读取浮点数
-
对齐修饰符:
-
用于左对齐,+
用于强制输出正负号,空格用于在正数前输出空格。例如:int var; scanf("%d", &var); // 默认读取整数
通过合理使用格式说明符和修饰符,scanf
函数可以实现灵活多样的格式化输入,满足各种编程需求。# 2. 格式说明符详解
2.1 常见格式说明符
scanf
函数的格式说明符是实现格式化输入的核心,以下是一些常见的格式说明符及其详细用法:
整数格式说明符
-
%d
或%i
:用于输入有符号十进制整数。这是最常用的整数格式说明符,适用于正负整数。int var; scanf("%d", &var); // 输入 123,var 的值为 123
-
%u
:用于输入无符号十进制整数。它适用于非负整数,输入时不会显示负号。unsigned int var; scanf("%u", &var); // 输入 456,var 的值为 456
-
%o
:用于输入无符号八进制整数。输入时需要以八进制形式输入,不显示前缀0
。int var; scanf("%o", &var); // 输入 377,var 的值为 255
-
%x
或%X
:用于输入无符号十六进制整数。%x
输入小写字母,%X
输入大写字母。int var; scanf("%x", &var); // 输入 ff,var 的值为 255 scanf("%X", &var); // 输入 FF,var 的值为 255
浮点数格式说明符
-
%f
:用于输入十进制浮点数。默认情况下,scanf
会读取小数点和数字,直到遇到非数字字符为止。float var; scanf("%f", &var); // 输入 3.14159,var 的值为 3.14159
-
%e
或%E
:用于输入科学计数法表示的浮点数。%e
输入小写字母e
,%E
输入大写字母E
。float var; scanf("%e", &var); // 输入 3.14159e+00,var 的值为 3.14159 scanf("%E", &var); // 输入 3.14159E+00,var 的值为 3.14159
字符和字符串格式说明符
-
%c
:用于输入单个字符。输入时只读取一个字符,包括空格和换行符。char var; scanf("%c", &var); // 输入 A,var 的值为 'A'
-
%s
:用于输入字符串。输入时会读取连续的字符,直到遇到空格、制表符或换行符为止。char str[10]; scanf("%s", str); // 输入 Hello,str 的值为 "Hello"
其他格式说明符
-
%p
:用于输入指针地址。输入时需要输入一个有效的指针地址。int *ptr; scanf("%p", &ptr); // 输入地址,ptr 的值为该地址
-
%%
:用于输入一个百分号%
。输入时需要输入一个%
符号。char var; scanf("%%", &var); // 输入 %,var 的值为 '%'
2.2 格式说明符的高级用法
格式说明符还可以结合多种修饰符来实现更复杂的格式化输入,以下是一些高级用法:
宽度修饰符
宽度修饰符用于指定输入的最大宽度。对于字符串,指定最大输入字符数。可以通过以下方式指定宽度:
-
固定宽度:直接指定一个整数。
char str[10]; scanf("%9s", str); // 最多读取 9 个字符
-
动态宽度:使用
*
表示宽度由后续参数指定。int width = 9; scanf("%*s", &width, str); // 最多读取 9 个字符
精度修饰符
精度修饰符用于控制输入的精度,对于浮点数和字符串有不同的含义:
-
浮点数:指定小数点后的位数。
float var; scanf("%f", &var); // 默认读取浮点数
-
字符串:指定最大输入长度。
char str[10]; scanf("%.9s", str); // 最多读取 9 个字符
跳过输入
使用 *
修饰符可以跳过某些输入项,不将其存储到变量中。例如:
int a, b;
scanf("%d %*d %d", &a, &b); // 输入 1 2 3,a 的值为 1,b 的值为 3
字段宽度
字段宽度修饰符用于指定输入字段的宽度。对于字符串,指定最大输入字符数。对于数字,指定最大输入数字的位数。例如:
int a;
scanf("%3d", &a); // 输入 1234,a 的值为 123
组合修饰符
可以将多种修饰符组合使用,以实现复杂的格式化需求:
char str[10];
scanf("%-9.5s", str); // 最多读取 9 个字符,字符串的最大长度为 5
```# 3. 输入控制与格式化
## 3.1 宽度与对齐
`scanf` 函数通过宽度修饰符和对齐修饰符来控制输入的宽度和对齐方式,从而实现更加灵活的输入控制。
### 宽度修饰符
宽度修饰符用于指定输入的最大宽度。对于字符串,指定最大输入字符数。对于数字,指定最大输入数字的位数。可以通过以下方式指定宽度:
- **固定宽度**:直接指定一个整数。例如:
```c
char str[10];
scanf("%9s", str); // 最多读取 9 个字符
- 动态宽度:使用
*
表示宽度由后续参数指定。这种方式更加灵活,可以根据实际需求动态调整宽度。例如:int width = 9; scanf("%*s", &width, str); // 最多读取 9 个字符
对齐修饰符
对齐修饰符用于控制输入的对齐方式,主要分为左对齐和右对齐:
-
左对齐:使用
-
修饰符。左对齐时,输入内容会靠左对齐,右侧填充空格。例如:char str[10]; scanf("%-9s", str); // 最多读取 9 个字符,左对齐
-
右对齐:默认对齐方式。右对齐时,输入内容会靠右对齐,左侧填充空格。例如:
char str[10]; scanf("%9s", str); // 最多读取 9 个字符,右对齐
实际应用示例
在实际编程中,宽度和对齐修饰符常用于控制输入数据的格式,使输入更加规范。例如,输入一个包含多个字段的数据时,可以使用宽度修饰符限制每个字段的输入长度:
int id;
char name[20];
float score;
scanf("%3d %-19s %f", &id, name, &score); // 输入格式:ID(最多 3 位)+ 名字(最多 19 个字符)+ 分数
通过合理使用宽度和对齐修饰符,可以实现更加灵活和规范的输入控制,提升程序的健壮性和用户体验。
3.2 精度控制
精度修饰符用于控制输入的精度,对于浮点数和字符串有不同的含义。通过精度修饰符,可以实现更加精确的输入控制,满足不同的需求。
浮点数精度
对于浮点数,精度修饰符指定小数点后的位数。默认情况下,scanf
会读取小数点和数字,直到遇到非数字字符为止。可以通过精度修饰符来调整。例如:
float var;
scanf("%f", &var); // 默认读取浮点数
scanf("%.2f", &var); // 读取小数点后最多 2 位
字符串精度
对于字符串,精度修饰符指定最大输入长度。如果输入的字符串长度大于指定的精度值,则会截断输入。例如:
char str[10];
scanf("%s", str); // 默认读取字符串,直到遇到空格或换行符
scanf("%.9s", str); // 最多读取 9 个字符
动态精度
精度值也可以通过 *
动态指定,这种方式更加灵活。例如:
int precision = 3;
scanf("%.*s", &precision, str); // 最多读取 3 个字符
实际应用示例
在实际编程中,精度控制常用于限制输入数据的长度,避免缓冲区溢出。例如,输入用户的名字时,限制名字的最大长度为 10 个字符:
char name[11];
scanf("%.10s", name); // 最多读取 10 个字符
对于浮点数,精度控制可以用于限制输入的小数位数,确保输入数据的精度。例如,输入一个包含小数的计算结果时,限制小数位数为 2 位:
float result;
scanf("%.2f", &result); // 读取小数点后最多 2 位
通过合理使用精度修饰符,可以实现更加精确和灵活的输入控制,满足各种复杂的编程需求。# 4. scanf 的返回值
4.1 返回值的含义
scanf
函数的返回值是一个整数,表示成功读取并赋值的输入项数。例如:
int a, b;
int result = scanf("%d %d", &a, &b);
如果用户输入了两个有效的整数,如 123 456
,则 result
的值为 2
,表示成功读取了两个输入项并分别赋值给变量 a
和 b
。
返回值的含义如下:
- 正整数:表示成功读取并赋值的输入项数。例如,返回值为
2
表示成功读取了两个输入项。 - 0:表示没有输入项成功读取并赋值。这通常发生在输入格式与格式说明符不匹配时。例如,格式说明符为
%d
,但用户输入的是一个非数字字符,如abc
,则返回值为0
。 - EOF:表示输入结束或发生读取错误。在标准输入中,EOF 通常由特定的输入信号触发,如在 Windows 系统中按
Ctrl+Z
,在 Linux 系统中按Ctrl+D
。此时,返回值为EOF
(通常定义为-1
)。
4.2 返回值的应用场景
scanf
的返回值在实际编程中具有多种应用场景,以下是一些常见的例子:
4.2.1 输入校验
通过检查 scanf
的返回值,可以判断输入是否成功。如果返回值为负数(如 EOF
),说明输入过程中发生了错误或输入结束;如果返回值为 0
,说明输入格式不匹配。例如:
int a, b;
int result = scanf("%d %d", &a, &b);
if (result == 2) {
printf("Both inputs are valid: a = %d, b = %d\n", a, b);
} else if (result == 1) {
printf("Only the first input is valid: a = %d\n", a);
} else if (result == 0) {
printf("Invalid input format\n");
} else if (result == EOF) {
printf("Input ended or error occurred\n");
}
这种方式可以有效避免因输入错误而导致的潜在问题,提高程序的健壮性。
4.2.2 循环读取输入
在需要循环读取输入时,scanf
的返回值可以作为循环条件,确保只有在输入成功时才继续处理。例如,读取多个整数直到输入结束:
int num;
while (scanf("%d", &num) == 1) {
printf("You entered: %d\n", num);
}
printf("Input ended\n");
在这个例子中,只要 scanf
成功读取一个整数,循环就会继续;如果输入结束或发生错误(返回值为 EOF
或 0
),循环就会终止。
4.2.3 动态调整输入逻辑
根据 scanf
的返回值,可以动态调整后续的输入逻辑。例如,在读取用户输入时,如果输入格式不匹配,可以提示用户重新输入:
int a, b;
while (scanf("%d %d", &a, &b) != 2) {
printf("Invalid input format. Please enter two integers:\n");
}
printf("Valid inputs: a = %d, b = %d\n", a, b);
这种方式可以确保用户输入符合预期格式,提升程序的用户体验。
4.2.4 调试与日志记录
在调试过程中,scanf
的返回值可以作为日志记录的一部分,帮助开发者了解程序的运行状态。例如:
int a, b;
int result = scanf("%d %d", &a, &b);
fprintf(stderr, "scanf result: %d\n", result);
if (result == 2) {
printf("Both inputs are valid: a = %d, b = %d\n", a, b);
} else {
fprintf(stderr, "Invalid input format\n");
}
这种方式可以将调试信息和错误信息区分开来,便于后续的分析和排查问题。# 5. scanf 的安全问题与注意事项
5.1 格式化字符串漏洞
scanf
函数在使用过程中存在一些安全隐患,尤其是格式化字符串漏洞。这种漏洞主要源于 scanf
函数对格式化字符串的处理机制。scanf
函数会根据格式化字符串中的格式说明符依次从输入流中读取数据并存储到指定的变量中。如果格式化字符串中的格式说明符数量多于实际提供的变量数量,scanf
函数会继续从输入流中读取未定义的数据,从而可能导致信息泄露或程序崩溃。
格式化字符串漏洞的成因
-
用户可控的格式化字符串:如果程序允许用户输入格式化字符串,攻击者可以通过精心构造的格式化字符串读取输入流中的敏感信息或修改程序的执行流程。例如:
char user_input[100]; scanf("%s", user_input); printf(user_input);
如果用户输入的是类似
%s %d
的格式化字符串,而程序没有提供相应的参数,printf
函数会从堆栈中读取未定义的数据并输出,可能导致信息泄露。 -
格式说明符与参数不匹配:如果格式化字符串中的格式说明符与实际提供的变量类型或数量不匹配,可能会导致未定义行为。例如:
scanf("%d", "Hello");
这里格式说明符
%d
期望一个整数变量的地址,但实际提供的是一个字符串,这会导致程序行为异常。
格式化字符串漏洞的利用
攻击者可以通过以下方式利用格式化字符串漏洞:
-
信息泄露:通过格式化字符串读取输入流中的数据,攻击者可以获取程序的内存布局、指针地址等敏感信息。例如:
scanf("%p %p %p");
这会输出输入流中的前几个指针地址,攻击者可以利用这些信息进一步构造攻击。
-
修改程序执行流程:通过格式化字符串中的
%n
格式说明符,攻击者可以向指定的内存地址写入数据,从而修改程序的执行流程。例如:int *ptr = (int *)0x12345678; scanf("%100d%n", 0, ptr);
这会将 100 写入地址为
0x12345678
的内存位置,从而可能改变程序的控制流。
格式化字符串漏洞的实例
以下是一个典型的格式化字符串漏洞实例:
#include <stdio.h>
#include <string.h>
void print_message(const char *msg) {
printf(msg);
}
int main() {
char user_input[100];
printf("Enter your message: ");
scanf("%99s", user_input);
print_message(user_input);
return 0;
}
如果用户输入的是类似 %s %d
的格式化字符串,printf
函数会从堆栈中读取未定义的数据并输出,可能导致信息泄露。如果用户输入的是类似 %n
的格式化字符串,攻击者可以向指定的内存地址写入数据,从而修改程序的执行流程。
5.2 安全使用建议
为了避免格式化字符串漏洞,建议在使用 scanf
函数时遵循以下安全使用建议:
1. 避免用户可控的格式化字符串
尽量避免将用户输入直接用作格式化字符串。如果需要根据用户输入动态生成格式化字符串,可以使用 snprintf
函数进行安全的格式化输出。例如:
char format[100];
snprintf(format, sizeof(format), "Value: %d", user_value);
printf(format);
2. 确保格式说明符与参数匹配
在使用 scanf
函数时,确保格式化字符串中的格式说明符与实际提供的变量类型和数量完全匹配。例如:
int a, b;
scanf("%d %d", &a, &b);
3. 使用安全的格式化函数
在某些情况下,可以使用更安全的格式化函数,如 snprintf
或 asprintf
。这些函数提供了更多的安全机制,可以有效防止缓冲区溢出和格式化字符串漏洞。例如:
char buffer[100];
snprintf(buffer, sizeof(buffer), "Value: %d", 123);
printf("%s\n", buffer);
4. 检查 scanf
的返回值
通过检查 scanf
的返回值,可以判断输入是否成功。如果返回值为负数,说明输入过程中发生了错误,可以据此进行错误处理。例如:
int result = scanf("%d", &a);
if (result != 1) {
fprintf(stderr, "Invalid input format\n");
// 进一步的错误处理
}
5. 避免使用 %n
格式说明符
%n
格式说明符允许向指定的内存地址写入数据,这可能会被攻击者利用来修改程序的执行流程。因此,尽量避免使用 %n
格式说明符。如果必须使用,应确保目标地址是安全的。
6. 使用编译器的安全检查功能
现代编译器提供了多种安全检查功能,可以检测格式化字符串漏洞。例如,GCC 编译器提供了 -Wformat
和 -Wformat-security
选项,可以检测格式化字符串中的潜在问题。启用这些选项可以提前发现潜在的安全问题。例如:
gcc -Wformat -Wformat-security -o program program.c
通过以上安全使用建议,可以有效避免格式化字符串漏洞,提高程序的安全性和稳定性。# 6. scanf 的变体
6.1 fscanf
fscanf
是 scanf
的一个变体,用于从指定的文件流中读取格式化输入,而不是从标准输入。其函数原型如下:
int fscanf(FILE *stream, const char *format, ...);
FILE *stream
:指定输入的文件流。可以是通过fopen
打开的文件流,也可以是标准输入流(如stdin
)。const char *format
:格式控制字符串,与scanf
的格式字符串相同。...
:可变参数列表,与scanf
的参数列表相同。
使用场景
-
文件读取:从文件中读取格式化数据。例如,读取一个包含学生信息的文件:
FILE *file = fopen("students.txt", "r"); if (file) { int id; char name[50]; float score; while (fscanf(file, "%d %s %f", &id, name, &score) == 3) { printf("ID: %d, Name: %s, Score: %.2f\n", id, name, score); } fclose(file); }
-
标准输入:虽然
fscanf
可以用于标准输入,但通常更推荐使用scanf
。不过在某些情况下,fscanf(stdin, ...)
也可以实现与scanf
相同的功能。
返回值
fscanf
的返回值与 scanf
相同,表示成功读取并赋值的输入项数。如果发生错误或输入结束,返回值为 EOF
。
6.2 sscanf
sscanf
是 scanf
的另一个变体,用于从字符串中读取格式化输入,而不是从标准输入或文件流。其函数原型如下:
int sscanf(const char *str, const char *format, ...);
const char *str
:输入的字符串,从中读取格式化数据。const char *format
:格式控制字符串,与scanf
的格式字符串相同。...
:可变参数列表,与scanf
的参数列表相同。
使用场景
-
字符串解析:从字符串中提取特定格式的数据。例如,解析一个包含日期和时间的字符串:
char input[] = "2024-05-10 12:34:56"; int year, month, day, hour, minute, second; sscanf(input, "%d-%d-%d %d:%d:%d", &year, &month, &day, &hour, &minute, &second); printf("Year: %d, Month: %d, Day: %d, Hour: %d, Minute: %d, Second: %d\n", year, month, day, hour, minute, second);
-
用户输入验证:在处理用户输入时,可以先将输入存储到字符串中,然后使用
sscanf
进行格式验证。例如:char input[100]; printf("Enter a number: "); fgets(input, sizeof(input), stdin); int num; if (sscanf(input, "%d", &num) == 1) { printf("You entered: %d\n", num); } else { printf("Invalid input\n"); }
返回值
sscanf
的返回值与 scanf
相同,表示成功读取并赋值的输入项数。如果格式不匹配或输入为空,返回值为 0
。# 7. 实际应用示例
7.1 日常编程中的使用场景
scanf
函数在日常编程中有着广泛的应用,以下是一些常见的使用场景及其示例代码:
1. 输入用户数据
在开发程序时,经常需要从用户那里获取数据。scanf
是一种非常方便的工具,可以帮助开发者快速获取用户输入的数据。例如,获取用户的基本信息:
int age;
float height;
char name[50];
printf("请输入您的姓名:");
scanf("%s", name);
printf("请输入您的年龄:");
scanf("%d", &age);
printf("请输入您的身高(米):");
scanf("%f", &height);
printf("您的信息如下:\n姓名:%s\n年龄:%d\n身高:%.2f米\n", name, age, height);
2. 读取配置文件
在某些程序中,需要从配置文件中读取参数。假设配置文件的格式为 key=value
,可以使用 fscanf
从文件中读取这些参数:
FILE *file = fopen("config.txt", "r");
if (file) {
char key[50], value[100];
while (fscanf(file, "%[^=]=%s", key, value) == 2) {
printf("Key: %s, Value: %s\n", key, value);
}
fclose(file);
}
3. 输入表格数据
在处理表格数据时,scanf
可以通过宽度和对齐修饰符来控制输入数据的格式,使输入更加规范。例如,输入一个包含多个字段的数据时,可以使用宽度修饰符限制每个字段的输入长度:
int id;
char name[20];
float score;
printf("请输入学生信息(格式:ID 名字 分数):");
scanf("%3d %-19s %f", &id, name, &score);
printf("学生信息:ID=%d, 名字=%s, 分数=%.2f\n", id, name, score);
4. 输入带条件的数据
在某些情况下,需要根据条件输入不同的数据。例如,根据用户的选择输入不同的信息:
int choice;
printf("请选择输入类型(1-整数,2-浮点数,3-字符串):");
scanf("%d", &choice);
switch (choice) {
case 1:
int num;
printf("请输入一个整数:");
scanf("%d", &num);
printf("您输入的整数是:%d\n", num);
break;
case 2:
float fnum;
printf("请输入一个浮点数:");
scanf("%f", &fnum);
printf("您输入的浮点数是:%.2f\n", fnum);
break;
case 3:
char str[50];
printf("请输入一个字符串:");
scanf("%s", str);
printf("您输入的字符串是:%s\n", str);
break;
default:
printf("无效的选择\n");
}
5. 输入带时间戳的数据
在日志系统中,通常需要在每条日志信息前加上时间戳。可以通过 scanf
从用户输入中获取带时间戳的数据:
#include <time.h>
time_t now = time(NULL);
struct tm *tm_info = localtime(&now);
char logmsg[100];
printf("请输入日志信息:");
scanf("%99s", logmsg);
printf("[%04d-%02d-%02d %02d:%02d:%02d] Log: %s\n",
tm_info->tm_year + 1900, tm_info->tm_mon + 1, tm_info->tm_mday,
tm_info->tm_hour, tm_info->tm_min, tm_info->tm_sec, logmsg);
7.2 复杂格式化输入示例
scanf
函数的强大之处在于其能够实现复杂的格式化输入,以下是一些复杂格式化输入的示例及其代码:
1. 输入多行格式化数据
在某些情况下,需要输入多行格式化数据,每行包含不同的信息。例如,输入一个包含多个字段的报告:
int id;
char name[50];
float score;
char grade[2];
printf("请输入学生信息(格式:ID 名字 分数 等级):\n");
scanf("%d %s %f %s", &id, name, &score, grade);
printf("学生信息:\nID: %d\n名字: %s\n分数: %.2f\n等级: %s\n", id, name, score, grade);
2. 动态格式化输入
在某些情况下,格式化字符串和参数可能在运行时动态生成。例如,根据用户输入动态生成格式化字符串并输入:
char format[100];
snprintf(format, sizeof(format), "ID: %d, 名字: %s, 分数: %.2f");
int id;
char name[50];
float score;
printf("请输入学生信息(格式:ID 名字 分数):");
scanf("%d %s %f", &id, name, &score);
printf(format, id, name, score);
3. 输入带有颜色的文本
在某些终端中,可以通过转义序列输入带有颜色的文本。例如,输入红色的错误信息:
char error_msg[100];
printf("请输入错误信息:");
scanf("%99s", error_msg);
printf("\033[31mError: %s\033[0m\n", error_msg);
4. 输入带有条件的文本
在某些情况下,需要根据条件输入不同的文本。例如,根据成绩输入不同的评价:
float score;
printf("请输入成绩:");
scanf("%f", &score);
if (score >= 90) {
printf("成绩:%.2f - 优秀\n", score);
} else if (score >= 80) {
printf("成绩:%.2f - 良好\n", score);
} else {
printf("成绩:%.2f - 需要改进\n", score);
}
5. 输入带有时间戳的日志信息
在日志系统中,通常需要在每条日志信息前加上时间戳。可以通过 scanf
从用户输入中获取带时间戳的日志信息:
#include <time.h>
time_t now = time(NULL);
struct tm *tm_info = localtime(&now);
char logmsg[100];
printf("请输入日志信息:");
scanf("%99s", logmsg);
printf("[%04d-%02d-%02d %02d:%02d:%02d] Log: %s\n",
tm_info->tm_year + 1900, tm_info->tm_mon + 1, tm_info->tm_mday,
tm_info->tm_hour, tm_info->tm_min, tm_info->tm_sec, logmsg);