标准输出函数

fprintfprintfsprintfdprintf 是 C 语言中的标准输入输出函数,用于格式化输出数据到不同的目标(如文件、标准输出、字符串等)。它们都属于格式化输出家族,核心原理是基于格式化字符串输出特定的变量值。下面是它们的详细介绍:

1. fprintf

int fprintf(FILE *stream, const char *format, ...);
  • 作用:向指定的文件流 stream 中输出格式化的字符串。

  • 常见用法:用于将数据写入文件或其他输出流(如标准错误 stderr)。

  • 参数

  • stream:指定目标流,常见的流包括 stdout(标准输出)、stderr(标准错误输出)或文件流 FILE *

  • format:格式化字符串,用于指定输出内容的格式。

  • 后面的参数:可变参数,表示具体要输出的数据。

  • 返回值:成功时返回写入字符的数量,失败时返回负值。

示例

FILE *file = fopen("output.txt", "w");
if (file) {
    fprintf(file, "Hello, %s! You are %d years old.\n", "Alice", 25);
    fclose(file);
}

2. printf

int printf(const char *format, ...);
  • 作用:将格式化的字符串输出到标准输出(stdout)。

  • 常见用法:最常见的输出函数,用于在控制台显示数据。

  • 参数

  • format:格式化字符串,用于指定输出内容的格式。

  • 后面的参数:可变参数,表示具体要输出的数据。

  • 返回值:成功时返回写入字符的数量,失败时返回负值。

示例

printf("Hello, %s! You are %d years old.\n", "Alice", 25);

3. sprintf

int sprintf(char *str, const char *format, ...);
  • 作用:将格式化的字符串输出到指定的字符数组 str 中。

  • 常见用法:将数据格式化为字符串存储,而不是输出到文件或标准输出。

  • 参数

  • str:输出的目标字符数组。

  • format:格式化字符串,用于指定输出内容的格式。

  • 后面的参数:可变参数,表示具体要输出的数据。

  • 返回值:成功时返回写入字符的数量(不包括终止字符 \0),失败时返回负值。

  • 注意sprintf 不会检查缓冲区的大小,容易导致缓冲区溢出,应优先使用更安全的 snprintf

示例

char buffer[100];
sprintf(buffer, "Hello, %s! You are %d years old.", "Alice", 25);
printf("%s\n", buffer);  // 输出:Hello, Alice! You are 25 years old.

4. dprintf

int dprintf(int fd, const char *format, ...);
  • 作用:向指定的文件描述符 fd 输出格式化的字符串。

  • 常见用法:可以直接写入特定的文件描述符,类似于 printffprintf,但使用文件描述符而不是 FILE * 流。

  • 参数

  • fd:目标文件描述符(常见的值包括 1 表示 stdout2 表示 stderr,以及通过系统调用如 open 返回的文件描述符)。

  • format:格式化字符串,用于指定输出内容的格式。

  • 后面的参数:可变参数,表示具体要输出的数据。

  • 返回值:成功时返回写入字符的数量,失败时返回负值。

示例

int fd = open("output.txt", O_WRONLY | O_CREAT, 0644);
if (fd != -1) {
    dprintf(fd, "Hello, %s! You are %d years old.\n", "Alice", 25);
    close(fd);
}

各函数的对比与应用场景

|函数|目标|用途|备注| |-|-|-|-| |fprintf|文件流 FILE *|输出到文件或流|适用于将数据输出到文件、标准错误等| |printf|标准输出 stdout|输出到控制台|最常用的输出函数,默认输出到控制台| |sprintf|字符数组 char *|输出到字符串|将格式化数据保存到字符串,易溢出风险| |dprintf|文件描述符 int|输出到文件描述符|适用于文件描述符输出,如文件、管道等|

https://blog.csdn.net/u012763794/article/details/60955530修改标志位

示例

printf1

#include <stdio.h>

void main()
{
    printf("%s %d %s","Hello World!",233,"\n");

}

gcc -m32 -o printf1 ./printf1

我们打一个断点到printf b *0x000011c2,run

已经能看到这个三个参数压入栈了,顺序是从右到左入栈,因为栈是LIFO,所以从右到左入栈,最后才能正确的从左到右出栈。这时候我们三个百分号对应的是后续的三个参数。

单步输出之后,输出Hello World! 233 \n

printf2

#include <stdio.h>

void main()
{
    printf("%s %d %s %x %x %x %3$s","Hello World!",233,"\n");

}

这时候我们运行第二个程序,我们多写一点格式化字符串。

会发现打印了一些奇怪的值,我们进入gdb ,还是打断点到printf

0xe9就是233的十六进制

这时候我们发现,除了前面的Hello World! 233 \n 之后还多了ffffd020 f7fa4e34 0 \n

如果格式化字符串后面参数不够,自动会从栈上自动寻找,进而可以输出栈上内容。

printf3

#include <stdio.h>

void main()
{
    char a[100];
    if(fgets(a,sizeof a,stdin)==NULL)
        return;
    printf(a);

}

printf3就是读入一个数据,然后如果是0那就return 如果不是就打印。我们编译之后gdb

这时候我们打断点到fgets,run之后单步,然后输入kiciot %x %x %x %x

能看到已经入栈了,我们继续单步到printf

发现除了kiciot之外,还打印了四个栈上的内容。所以存在格式化字符串漏洞,我们就可以利用格式化字符串漏洞去泄露栈上的内容