在C中传递字符串最安全的方法是什么?

时间:2010-04-15 00:00:54

标签: c string unix pointers

我在使用Solaris的C程序中看起来很古老。许多例子,即使是在SO上,也不起作用,以及我在Mac OS X上编写的大量代码。

所以当使用非常严格的C时,传递字符串最安全的方法是什么?

我目前正在使用各种各样的char指针,因为我认为简单。所以我有返回char *的函数,我将char *传递给它们等等。

我已经看到了奇怪的行为,就像一个char *我在输入一个函数时传递了它的值,然后在一个简单的像printf()或malloc之类的东西之后,这个值被神秘地消失了或被破坏/覆盖了其他指针。

我确信这些功能的一种方法可能是:

char *myfunction(char *somestr) {    
  char localstr[MAX_STRLENGTH] = strcpy(localstr, somestr);
  free(somestr);
  /* ... some work ... */
  char *returnstr = strdup(localstr);
  return returnstr;
}

这似乎......马虎。任何人都可以指出我在一个简单的要求上正确的方向吗?

更新

一个功能的例子,我对正在发生的事情感到茫然。不确定这是否足以解决这个问题,但这里有:'

char *get_fullpath(char *command, char *paths) {
  printf("paths inside function %s\n", paths); // Prints value of paths just fine

  char *fullpath = malloc(MAX_STRLENGTH*sizeof(char*));

  printf("paths after malloc %s\n", paths); // paths is all of a sudden just blank
}

3 个答案:

答案 0 :(得分:12)

编写良好的C代码符合以下惯例:

       
  • 所有函数都返回类型为 int 的状态代码,其中返回值0表示成功,-1表示失败。如果失败,该函数应将errno设置为适当的值(例如EINVAL)。
  •    
  • 应通过使用“out参数”报告函数“报告”的值。换句话说,其中一个参数应该是指向目标对象的指针。
  •    
  • 指针的所有权应该属于调用者;因此,一个函数不应该free任何参数,并且只应该free它自己用malloc/calloc分配的对象。
  •    
  • 字符串应作为const char*对象或char*对象传递,具体取决于是否要覆盖该字符串。如果不修改字符串,则应使用const char*
  •    
  • 每当传递的数组不是以NUL结尾的字符串时,应提供一个参数,指示数组中元素的数量或该数组的容量。
  •    
  • 当一个可修改的字符串/缓冲区(即char*)对象传递给一个函数,并且该函数是覆盖,追加或以其他方式修改字符串时,一个参数指示字符串/缓冲区需要的容量提供(以便允许动态缓冲区大小和避免缓冲区溢出)。

我应该指出,在您的示例代码中,您将返回localstr而不是returnstr。因此,您将返回当前函数的堆栈帧中的对象的地址。函数返回后,当前函数的堆栈帧将消失。之后立即调用另一个函数可能会改变该位置的数据,从而导致您观察到的损坏。返回局部变量的地址会导致“未定义的行为”并且不正确。

修改
根据您更新的代码(get_fullpath),很明显问题不在您的函数get_fullpath中,而是在调用它的函数中。最有可能的是,paths变量由一个返回局部变量地址的函数提供。因此,当您在get_fullpath中创建局部变量时,它在堆栈上使用与先前占用的路径相同的确切位置。由于“paths”是别名“fullpaths”,它基本上被你已经过malloced的缓冲区的地址覆盖,这是空白的。

编辑2
我在C Coding Conventions上创建了一个my website页面,其中包含更详细的建议,解释和编写C代码的示例,以备您感兴趣。此外,自上次编辑问题以来,返回localstr而不是returntr的语句不再成立。

答案 1 :(得分:4)

您不能返回指向在函数内本地分配的数组的指针。一旦函数返回,该数组将被破坏。

另外,当你把

char localstr[MAX_STRLENGTH] = strcpy(localstr, somestr);

会发生什么是strcpy()会将字节复制到localstr []数组中,但是你有一个不必要的赋值事情。你可能会得到两行的预期效果,因此......

char localstr[MAX_STRLENGTH];
strcpy(localstr, somestr);

另外,在像这样的函数中嵌入free()调用是不好的形式。理想情况下,free()应该在malloc()发生的同一范围内可见。按照同样的逻辑,以这种方式在函数中分配内存有点不确定。

如果你想要一个函数来修改一个字符串,常见的约定就是这样

// use a prototype like this to use the same buffer for both input and output
int modifyMyString(char buffer[], int bufferSize) {
    // .. operate you find in buffer[],
    //    leaving the result in buffer[]
    //    and be sure not to exceed buffer length
    // depending how it went, return EXIT_FAILURE or maybe
    return EXIT_SUCCESS;

// or separate input and outputs
int workOnString(char inBuffer[], int inBufSize, char outBuffer[], int outBufSize) {
    // (notice, you could replace inBuffer with const char *)
    // leave result int outBuffer[], return pass fail status
    return EXIT_SUCCESS;

不在内部嵌入malloc()或free()也有助于避免内存泄漏。

答案 2 :(得分:0)

您的“更新”示例是否完整?我不认为会编译:它会调用返回值,但你永远不会返回任何东西。你永远不会做任何事情的全路径,但也许这是故意的,也许你的观点只是说当你做malloc时,其他事情就会破裂。

如果没有看到来电者,就不可能明确地说出这里发生了什么。我的猜测是路径是一个动态分配的块,在你调用这个函数之前是免费的。根据编译器的实现,freed块仍然可能看起来包含有效数据,直到将来的malloc占用空间为止。

更新:实际回答问题

字符串处理是C中一个众所周知的问题。如果创建一个固定大小的数组来保存字符串,则必须担心长字符串会溢出分配的空间。这意味着不断检查副本上的字符串大小,使用strncpy和strncat而不是简单的strcpy和strcat,或类似的技术。你可以跳过这个,然后说,“好吧,没有人会有一个超过60个字符的名字”或者其他一些这样的名字,但是总会有人会这样做的危险。即使在应该具有已知大小的东西上,例如社会安全号码或ISBN,有人可能会错误地输入它并按两次键,或者恶意用户可能故意输入长时间的东西。等等。当然这主要是数据输入或读取文件的问题。一旦你在一个已知大小的字段中有一个字符串,那么对于任何副本或其他操作,你就知道它的大小。

另一种方法是使用动态分配的缓冲区,您可以根据需要调整它们。这听起来像是一个很好的解决方案,当你第一次听到它时,但在实践中它是C的一个巨大的痛苦,因为分配缓冲区并在你不再需要它们时释放它们是很麻烦的。这里的另一张海报说,分配缓冲区的函数应该与释放缓冲区的函数相同。一个好的经验法则,我普遍同意,但是......如果一个子程序想要返回一个字符串怎么办?所以它分配缓冲区,返回它,然后......它怎么能释放它呢?它不能,因为重点是它想要将它返回给调用者。调用者无法分配缓冲区,因为它不知道大小。此外,看似简单的事情,如:

if (strcmp(getMeSomeString(),stringIWantToCompareItTo)==0) etc

是不可能的。如果getMeSomeString函数分配了字符串,当然,它可以返回它,所以我们进行比较,但现在我们已经丢失了句柄,我们永远无法释放它。你最终不得不编写像

这样的尴尬代码
char* someString=getMeSomeString();
int f=strcmp(someString,stringIWantToCompareItTo);
free(someString);
if (f==0)
etc

好吧,它确实有效,但可读性刚刚下降。

在实践中,我发现当合理地预期字符串具有可知的大小时,我会分配固定长度的缓冲区。如果输入大于缓冲区,我要么截断它,要么给出错误消息,具体取决于上下文。当大小可能很大且不可预测时,我只求助于动态分配的缓冲区。