【问题标题】:C sprintf overwrite?C sprintf 覆盖?
【发布时间】:2014-10-04 05:53:00
【问题描述】:

谁能解释一下为什么这不起作用?看来程序停在 sprintf(t->tuple[0], "abc");我不知道为什么。真的需要帮助。

int testRemoveMeio(){

  int result, i;
  struct list_t *list = list_create();

  char *tdata[3] = {"   ", "2014", "Fixe!"};
  struct tuple_t *t = tuple_create2(3, tdata);
  struct tuple_t *tdups[4];

  struct entry_t *entries[4];

  sprintf(t->tuple[0], "abc");
  tdups[0] = tuple_dup(t);
  entries[0] = entry_create(tdups[0]);
  list_add(list, entries[0]);

  //extra code similar to above
  return 0;
}

编辑:

struct tuple_t {
    int tuple_dimension; 
    char **tuple;        
};

struct tuple_t *tuple_create2(int tuple_dim, char **tuple){

    struct tuple_t *t = (struct tuple_t *) malloc(sizeof(struct tuple_t));

    if(t == NULL)
        return NULL;

    t->tuple_dimension = tuple_dim;
    t->tuple = tuple;

    return t;
}

编辑添加来自 cmets 的另一个示例:

#include <stdio.h>
#include <string.h>
int main(void)
{
  char * s = "ABC";
  printf("%s\n",s);
  sprintf(s,"DEF");
  printf("%s\n",s); 
  return 0;
}

【问题讨论】:

  • 你没有给我们完整的工作代码,但无论如何你应该能够使用调试器来查明崩溃的位置,或者是否真的发生了崩溃(你说你的程序“停止”但这不是很精确)。
  • [回复 OP 的评论,后来被删除....] 我无法解释,因为我不是通灵者,但调试器可以在您的实际代码上运行并准确告诉您崩溃是。试一试,如果你仍然无法理解它,请回来。然后,我们至少需要查看tuple_create2() 的定义。
  • 我明白了。我可能应该给你完整的代码,但它太多了。相反,你能解释一下这个简单的事情以及为什么它不起作用吗? #include #include int main(void){ char * s = "ABC"; printf("%s\n",s); sprintf(s,"DEF"); printf("%s\n",s);返回0; }
  • 永远不要在 2014 年使用 sprintf。在 char 的一些 数组 或一些堆分配的 char 区域上使用 snprintf(3)。或者如果你的系统有asprintf(3)
  • 可以工作。实际上它不是因为s 是用"ABC" 初始化的,这是一个常量字符串。通常,编译器将常量字符串分配到内存的只读段中。所以s = "ABC" 用内存只读部分的地址初始化s。因此,当您尝试将某些内容复制到s(使用sprintf(s,"DEF") 或更好的strcpy(s, "DEF"))时,会发生错误,因为您尝试编写只读部分..

标签: c pointers struct printf


【解决方案1】:

我将您评论中的代码粘贴到问题中。我认为,这和您的原始代码都失败了,原因相同:您正在修改字符串文字。

在 C 语言中,可悲但真实的是,像 "hello world" 这样的字符串文字是非法修改的,但可以在没有 const 和没有强制转换的情况下分配给 char*。这很容易搞砸,因为修改这样的字符串是未定义的行为,因为它可能驻留在程序的只读内存中。

要修复它,试试这个:

char tdata[3][6] = {"   ", "2014", "Fixe!"};

或者在你的第二个例子中:

char s[] = "ABC";

通过这种方式,您可以分配一个具有可写存储空间的实际字符数组。但是,请始终使用snprintf() 而永远不要使用sprintf(),因为后者容易出现缓冲区溢出,这可能会使您的程序崩溃或更糟。

【讨论】:

  • 明白了。但问题是我给你的这段代码是老师给的,用来测试我们自己的代码,所以它必须工作。我尝试了第二个,它成功了,非常感谢。
  • 如果你的老师给你char *tdata[3] = {" ", "2014", "Fixe!"};并告诉你修改字符串,你需要先将字符串复制到内存中的一个可修改数组中。或者,如果您只想用其他文字字符串替换字符串,tdata[0] = "hi" 之类的东西就可以了。
  • 你是对的。 tdata[0] = "hi" 有效,但我无法触摸此代码。很抱歉对于这个误会。这是我制作的另一个代码的测试代码。我添加到该代码的问题部分。你现在能看出什么问题了吗?还是您需要我添加更多代码?再次非常感谢
【解决方案2】:

你的第二个例子是错误的。您应该在 char 的某些 array 上使用 snprintf(3)(没有理由在 2014 年使用 sprintf;它既古老又危险,因为它可能会给出 buffer overflow)像这样:

#include <stdio.h>
int main(void) {
  char buf[64];
  snprintf(buf, sizeof(buf), "here %s", "ABC");
  printf("%s\n",buf); 
  return 0;
}

您可以使用一些动态分配的内存区域(但是,您应该知道它的大小),例如

#include <stdio.h>
#include <stdlib.h>
int main(void) {
  size_t siz=32;
   char* ptr = malloc(siz);
   if (!ptr) { perror("malloc"); exit(EXIT_FAILURE); };
   snprintf(ptr, siz, "here %s and two is %d", "ABC", 2);
   printf("%s\n", ptr);
   free (ptr);
   return 0;
}

如果您的系统提供它(例如在 Linux 上),您可以使用 asprintf(3)

#define _GNU_SOURCE
#include <stdio.h>
int main (void) {
  char* ptrzone = NULL;
  asprintf(&ptrzone, "and here %s and one is %d", "ABC", 1);
  printf("%s\n", ptrzone);
  free (ptrzone), ptrzone = NULL;
}

在严肃的程序中,你经常应该使用snprintf的结果。

阅读您正在使用的所有函数的文档,同时阅读:perror(3)exit(3)malloc(3)free(3)

你绝对应该阅读更多关于C dynamic memory allocation的信息

您应该使用所有警告和调试信息 (gcc -Wall -g) 进行编译,并学习使用调试器 (gdb) 和内存泄漏检测器 valgrind

【讨论】:

  • 如果您的系统没有 asprintf,使用 vsnprintf 滚动您自己的系统并不难。
【解决方案3】:

缺少一些重要信息,如 struct tuple_t 的定义和函数 tuple_create2 的定义。如果错误发生在 sprintf(t-&gt;tuple[0], "abc") 上,则 t-&gt;tuple 和/或 t-&gt;tuple[0] 未正确初始化/分配。对于示例来说,前者应该是一个至少为 1 的数组(但如果我们查看 tdata,则更可能是 3 个)字符串,而后者应该是一个字符串缓冲区(char 数组)。检查您或向我们提供tuple_create2 功能。

使用 sprintf 复制字符串的方式很危险。在您的示例中这有效,但如果要复制的字符串包含 '%' 字符,则可能会发生坏事(% 是用于在 printf/scanf 系列函数中指定格式的字符。如果您想继续使用 @987654330 @ 使用sprintf(t-&gt;tuple[0], "%s", "abc") 但更简单的是strcpy(t-&gt;tuple[0], "abc")

既然你现在已经添加了tuple_create2的定义,我试着回答一下。

在 C 中,由您来管理内存。你对tuple_create2 有什么期望?您的代码为“标题”(即包含维度和字符串数组的struct tuple)分配空间并用tuple 填充它。结果是一个新对象,它指向数据的相同内存(即字符串数组)。这没关系,除非您想修改此数组(似乎是这种情况)。有两件事:数组本身(每个条目都是一个指向字符数组的指针 - 你想修改指针吗?)和字符串(字符数组的内容)。有几种可能性,但这取决于你。例如,函数可以分配自己的指针数组并使用现有数据进行初始化:

struct tuple_t *tuple_create2(int tuple_dim, char **tuple){

    struct tuple_t *t = (struct tuple_t *) malloc(sizeof(struct tuple_t));

    if(t == NULL)
        return NULL;

    t->tuple_dimension = tuple_dim;
    t->tuple = (char **) malloc(sizeof(char *) * tuple_dim);

    for(i = 0; i < tuple_dim; i++)  // could be replaced by a memcpy
      t->tuple[0] = tuple[0];  

    return t;
}

那么你必须知道我们只复制了指针。所以当你想修改一个元素时,你只需复制指针:

 t->tuple[0] = "abc";    

或者你分配一个新的字符串,如果你认为它可以被修改并且你不想要这个。我给你一个“abc”的例子,但这不相关,因为“abc”是一个不会被修改的常量字符串。无论如何,这里是代码(注意使用stdrup,它复制了执行malloc的字符串)

t->tuple[0] = strdup("abc");

【讨论】:

  • struct tuple_t { int tuple_dimension; char **元组; }; struct tuple_t *tuple_create2(int tuple_dim, char **tuple){ struct tuple_t *t = (struct tuple_t *) malloc(sizeof(struct tuple_t));如果(t == NULL)返回 NULL; t->tuple_dimension = tuple_dim; t->元组=元组;返回 t; }
  • 我在问题中添加了 struct tuple_t 和 tuple_create2 。你能弄清楚那里出了什么问题吗?
  • @Miguel Mendes,我已经更新了我的答案,以考虑到你最后的代码。
猜你喜欢
  • 2011-06-11
  • 1970-01-01
  • 2017-12-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-07-18
  • 1970-01-01
  • 2012-12-17
相关资源
最近更新 更多