【问题标题】:how to store printf into a variable?如何将 printf 存储到变量中?
【发布时间】:2009-11-17 00:07:02
【问题描述】:

我想使用类似于 printf 在 C 中所做的事情来存储格式化字符串。

char *tmp = (char *)sqlite3_column_text(selectstmt, 2);
const char *sqlAnswers = printf("select key from answer WHERE key = %s LIMIT 5;", tmp);

后者显然是个错误。

【问题讨论】:

  • 最好是绑定参数的原因之一

标签: c string string-formatting


【解决方案1】:

您可以使用 sprintf 完成此操作,但不能单独(安全)。在一个健全的系统上,使用snprintf 两次,一次是找出要使用的大小,第二次是实际使用。这取决于snprintf 在空间不足时返回所需的字符数。 Linux、BSD 和 C99 兼容的系统可以做到这一点; Windows 通常不会。在后一种情况下,如果snprintf 失败(循环直到snprintf 成功),您需要分配一个初始缓冲区并分配一个更大的缓冲区。但在 C99 上,以下内容将起作用:

char *buf;
size_t sz;
sz = snprintf(NULL, 0, "select key from answer WHERE key = %s LIMIT 5;", tmp);
buf = (char *)malloc(sz + 1); /* make sure you check for != NULL in real code */
snprintf(buf, sz+1, "select key from answer WHERE key = %s LIMIT 5;", tmp);

然而,对于构建 SQL,使用prepared statements 会好得多。它们避免了 SQL 注入漏洞(并且经常需要sprintf)。使用它们,您将准备语句“select key from answer where key = ? limit 5;”,然后使用参数tmp 执行它。 SQL 引擎将字符串放入,无需先确保它正确转义。

【讨论】:

  • @Noldorin,几乎没有准备好的语句,您仍然可以将 '3;--drop table answer' 分配给 temp。
  • Sane 是一个符合 C99 标准的系统!一些 C89 实现提供了自己的 snprintf,其行为与 C99 描述的不同(返回值不一定是所需的长度)。
  • +1 表示正确答案、准备好的陈述、缓冲安全以及获得 2000 次代表所需的一票。不客气。
  • @Byron Whitlock:建议的“准备好的语句”的文本对于“3;drop table answer;--”的密钥以及您尝试的(但失败的)SQL 注入攻击的版本是安全的。跨度>
  • 我做了一个小修复,在 malloc 中使用 (char *) 来转换类型
【解决方案2】:

你想要sprintf()

char *sqlAnswers = malloc(SIZE_TO_HOLD_FINAL_STRING);
sprintf(sqlAnswers, "select key from answer WHERE key = %s LIMIT 5;", tmp);

【讨论】:

  • 为了安全起见,始终使用snprintf()
【解决方案3】:

如果您使用 gnu 或 BSD libc,您可以使用 asprintf,它会自动分配正确大小的缓冲区。

#define _GNU_SOURCE
#include <stdio.h>
// ...
char *sqlAnswers = NULL;
int length = asprintf(&sqlAnswers,"select key from answer WHERE key = %s LIMIT 5;", tmp);
free(sqlAnswers);

【讨论】:

  • asprintfsprintf(malloc(snprintf(...))) 技巧的便捷捷径——我投票赞成使用它并提供一个备用asprintf 定义,如果你不得不处理一个缺少它的悲伤、过时的平台.
【解决方案4】:

我实际上是使用 sqlite3_bind_text 来输入我的通配符,而不是通过 sprintf 生成:

const char *sql1 = "select id, repA, key from iphone_reponse WHERE question_id = ?;";
sqlite3_stmt *selectstmt1;
if(sqlite3_prepare_v2(database, sql1, -1, &selectstmt1, NULL) == SQLITE_OK) {
    sqlite3_bind_text(selectstmt1, 1, [questionObj.key UTF8String], -1, SQLITE_TRANSIENT);

【讨论】:

    【解决方案5】:

    Michael Ekstrand 代码很好,但您需要多次复制和粘贴。我在一个函数中使用此代码

    char *storePrintf (const char *fmt, ...)
    {
        va_list arg;
        va_start(arg, fmt);
        size_t sz = snprintf(NULL, 0, fmt, arg);
        char *buf = (char *)malloc(sz + 1);
        vsprintf(buf, fmt, arg);
        va_end (arg);
        return buf;
    }
    

    缓冲区溢出有问题吗?到现在为止我都没有问题。

    编辑。

    好的,我有一个问题,因为我正在使用 Arduino。它使用内存并且不会丢弃它,因此您需要在使用后将其删除。

    【讨论】:

      【解决方案6】:

      在 Windows 上,您可以使用 sprintf_s,它可以像 Michael E 所说的那样添加缓冲区溢出保护。

      http://msdn.microsoft.com/en-us/library/ce3zzk1k(VS.80).aspx

      【讨论】:

      • 如果缓冲区太小,sprintf_s 似乎不会返回所需的字节数; GNU 和 BSD snprintf 都可以。这是我所依赖的关键行为。
      猜你喜欢
      • 1970-01-01
      • 2019-10-07
      • 2019-11-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-08-06
      相关资源
      最近更新 更多