合并目录和文件路径 - C.

时间:2010-06-29 15:47:23

标签: c string refactoring

作为学习C的一部分,我编写了以下代码来将目录名与文件名组合在一起。例如:combine("/home/user", "filename")将导致/home/user/filename。这个功能可以跨平台工作(至少在所有流行的Linux发行版和Windows 32和64bit上)。

这是代码。

const char* combine(const char* path1, const char* path2)
{
    if(path1 == NULL && path2 == NULL) {
        return NULL;
    }

    if(path2 == NULL || strlen(path2) == 0) return path1;
    if(path1 == NULL || strlen(path1) == 0) return path2;

    char* directory_separator = "";
#ifdef WIN32
    directory_separator = "\\";
#else 
    directory_separator = "/";
#endif

    char p1[strlen(path1)];                    // (1)
    strcpy(p1, path1);                         // (2) 
    char *last_char = &p1[strlen(path1) - 1];  // (3)

    char *combined = malloc(strlen(path1) + 1 + strlen(path2));
    int append_directory_separator = 0;
    if(strcmp(last_char, directory_separator) != 0) {
        append_directory_separator = 1;
    }
    strcpy(combined, path1);
    if(append_directory_separator)
        strcat(combined, directory_separator);
    strcat(combined, path2);
    return combined;
}

关于上述代码,我有以下问题。

  1. 考虑编号为1,2,3的线。所有这三行都是从字符串中获取最后一个元素。看起来我正在为这么小的东西编写更多代码。从char*字符串中获取最后一个元素的正确方法是什么。
  2. 要返回结果,我使用malloc分配新字符串。我不确定这是否是正确的方法。调用者是否应该释放结果?如何指示呼叫者必须释放结果?是否存在一种不易出错的方法?
  3. 你如何评价代码(差,平均,好)?有哪些方面可以成为现实?
  4. 任何帮助都会很棒。

    修改

    修复了所讨论的所有问题并实施了建议的更改。这是更新的代码。

    void combine(char* destination, const char* path1, const char* path2)
    {
        if(path1 == NULL && path2 == NULL) {
            strcpy(destination, "");;
        }
        else if(path2 == NULL || strlen(path2) == 0) {
            strcpy(destination, path1);
        }
        else if(path1 == NULL || strlen(path1) == 0) {
            strcpy(destination, path2);
        } 
        else {
            char directory_separator[] = "/";
    #ifdef WIN32
            directory_separator[0] = '\\';
    #endif
            const char *last_char = path1;
            while(*last_char != '\0')
                last_char++;        
            int append_directory_separator = 0;
            if(strcmp(last_char, directory_separator) != 0) {
                append_directory_separator = 1;
            }
            strcpy(destination, path1);
            if(append_directory_separator)
                strcat(destination, directory_separator);
            strcat(destination, path2);
        }
    }
    

    在新版本中,调用者必须分配足够的缓冲区并发送到combine方法。这样可以避免使用mallocfree问题。这是用法

    int main(int argc, char **argv)
    {
        const char *d = "/usr/bin";
        const char* f = "filename.txt";
        char result[strlen(d) + strlen(f) + 2];
        combine(result, d, f);
        printf("%s\n", result);
        return 0;
    }
    

    有关更多改进的建议吗?

6 个答案:

答案 0 :(得分:4)

内存泄漏:

const char *one = combine("foo", "file");
const char *two = combine("bar", "");
//...
free(one);   // needed
free(two);   // disaster!

修改:您的新代码看起来更好。一些小的风格变化:

  • 第4行中的双分号;;
  • 在第6行中,将strlen(path2) == 0替换为path2[0] == '\0''或仅!path2[0]
  • 同样在第9行。
  • 删除循环确定last_char,然后使用const char last_char = path1[strlen(path1) - 1];
  • if(append_directory_separator)更改为if(last_char != directory_separator[0])。因此,您不再需要变量append_directory_separator
  • 让您的函数返回destination,类似于strcpy(dst, src),返回dst

编辑last_char的循环有一个错误:它总是返回path1的结尾,所以你最终可能会在你的答案中用双斜线// (但是Unix将把它视为单斜杠,除非它在开始时)。无论如何,我的建议修正了这一点 - 我看到它与jdmichal的答案非常相似。 我看到你的原始代码中有正确的代码(我承认我只是瞥了一眼 - 这对我来说太复杂了;你的新代码要好得多)。

还有两个,稍微更主观的意见:

  • 我会使用stpcpy(),以避免strcat()的低效率。 (如果需要,可以轻松编写自己的。)
  • 有些人对strcat()之类的观点非常强烈,认为不安全。但是,我认为你在这里的用法非常好。

答案 1 :(得分:2)

  1. 您使用last_char的唯一时间是在比较中检查最后一个字符是否为分隔符。
  2. 为什么不用这个替换它:

    /* Retrieve the last character, and compare it to the directory separator character. */
    char directory_separator = '\\';
    if (path1[strlen(path1) - 1] == directory_separator)
    {
        append_directory_separator = 1;
    }
    

    如果要考虑多个字符分隔符的可能性,可以使用以下内容。但是请确保在分配组合字符串时添加strlen(directory_separator)而不是仅添加1。

    /* First part is retrieving the address of the character which is
       strlen(directory_separator) characters back from the end of the path1 string.
       This can then be directly compared with the directory_separator string. */
    char* directory_separator = "\\";
    if (strcmp(&(path1[strlen(path1) - strlen(directory_separator)]), directory_separator))
    {
        append_directory_separator = 1;
    }
    
    1. 较少出错的方法是让用户为您提供目标缓冲区及其长度,就像strcpy的工作方式一样。这清楚表明他们必须管理分配和释放内存。

    2. 这个过程看起来不错。我认为只有一些细节可以解决,主要是以笨重的方式做事。但你表现得很好,因为你已经认识到这种情况并寻求帮助。

答案 2 :(得分:1)

也许我有点迟了,但是我以某种方式改进了更新的代码,使其也可以与“ /../”之类的东西一起使用。

/*
 * Combine two paths into one. Note that the function
 * will write to the specified buffer, which has to
 * be allocated beforehand.
 *
 * @dst: The buffer to write to
 * @pth1: Part one of the path
 * @pth2: Part two of the path
*/
void joinpath(char *dst, const char *pth1, const char *pth2)
{
    if(pth1 == NULL && pth2 == NULL) {
        strcpy(dst, "");
    }
    else if(pth2 == NULL || strlen(pth2) == 0) {
        strcpy(dst, pth1);
    }
    else if(pth1 == NULL || strlen(pth1) == 0) {
        strcpy(dst, pth2);
    } 
    else {
        char directory_separator[] = "/";
#ifdef WIN32
        directory_separator[0] = '\\';
#endif
        const char *last_char = pth1;
        while(*last_char != '\0')
            last_char++;        
        int append_directory_separator = 0;
        if(strcmp(last_char, directory_separator) != 0) {
            append_directory_separator = 1;
        }
        strcpy(dst, pth1);
        if(append_directory_separator)
            strcat(dst, directory_separator);
        strcat(dst, pth2);
    }

    char *rm, *fn;
    int l;
    while((rm = strstr (dst, "/../")) != NULL) {
        for(fn = (rm - 1); fn >= dst; fn--) {
            if(*fn == '/') {
                l = strlen(rm + 4);
                memcpy(fn + 1, rm + 4, l);
                *(fn + len + 1) = 0;
                break;
            }
        }
    }
}

答案 3 :(得分:0)

这就是我使用的:

#if defined(WIN32)
#  define DIR_SEPARATOR '\\'
#else
#  define DIR_SEPARATOR '/'
#endif

void combine(char *destination, const char *path1, const char *path2) {
  if (path1 && *path1) {
    auto len = strlen(path1);
    strcpy(destination, path1);

    if (destination[len - 1] == DIR_SEPARATOR) {
      if (path2 && *path2) {
        strcpy(destination + len, (*path2 == DIR_SEPARATOR) ? (path2 + 1) : path2);
      }
    }
    else {
      if (path2 && *path2) {
        if (*path2 == DIR_SEPARATOR)
          strcpy(destination + len, path2);
        else {
          destination[len] = DIR_SEPARATOR;
          strcpy(destination + len + 1, path2);
        }
      }
    }
  }
  else if (path2 && *path2)
    strcpy(destination, path2);
  else
    destination[0] = '\0';
}

答案 4 :(得分:0)

为了改善你的功能,请注意一点:

Windows确实支持路径中的'/''\\'分隔符。所以我应该能够执行以下调用:

const char* path1 = "C:\\foo/bar";
const char* path2 = "here/is\\my/file.txt";
char destination [ MAX_PATH ];
combine ( destination, path1, path2 );

编写多平台项目时的想法可能是在任何输入路径(从用户输入,加载的文件......)中将'\\'转换为'/',然后您只需要处理{ {1}}字符。

问候。

答案 5 :(得分:-1)

快速浏览一下:

  1. 您使用的是非标准C
  2. 的C ++注释(//)
  3. 你是在代码的一部分声明变量 - 也不是C.它们应该在函数的开头定义。
  4. #1处的字符串p1在#2处写入了太多字节,因为strlen返回字符串的长度,并且对于null终止符需要1个字节。
  5. malloc没有分配足够的内存 - 你需要path1的长度+ path2的长度+ separator的长度+ null终止符。