是否在C未定义的行为中使用嵌套函数调用连接任意数量的字符串?

时间:2018-02-15 22:20:24

标签: c function standards

我有一个应用程序,它通过一系列字符串连接来构建文件路径名,使用多个文本来创建一个完整的文件路径名。

问题在于,处理连接少量但任意数量的文本字符串的方法是否取决于 Undefined Behavior 是否成功。

是否保证了一系列嵌套函数的评估顺序?

我发现了这个问题Nested function calls order of evaluation但是它似乎更多地是关于参数列表中的多个函数而不是一系列嵌套函数。

请原谅以下代码示例中的名称。它与其余的源代码一致,我先测试一下。

我第一次需要连接几个字符串是一个看起来像下面的函数,它可以将三个文本字符串连接成一个字符串。

typedef wchar_t TCHAR;

TCHAR *RflCatFilePath(TCHAR *tszDest, int nDestLen, TCHAR *tszPath, TCHAR *tszPath2, TCHAR *tszFileName)
{
    if (tszDest && nDestLen > 0) {
        TCHAR *pDest = tszDest;
        TCHAR *pLast = tszDest;

        *pDest = 0;   // ensure empty string if no path data provided.

        if (tszPath) for (pDest = pLast; nDestLen > 0 && (*pDest++ = *tszPath++); nDestLen--) pLast = pDest;
        if (tszPath2) for (pDest = pLast; nDestLen > 0 && (*pDest++ = *tszPath2++); nDestLen--)  pLast = pDest;
        if (tszFileName) for (pDest = pLast; nDestLen > 0 && (*pDest++ = *tszFileName++); nDestLen--)  pLast = pDest;
    }

    return tszDest;
}

然后我遇到了一个案例,我将四段文字放在一起。

考虑到这一点,似乎很可能也会有一个案例,很快就会发现五个,所以我想知道对于任意数量的字符串是否有不同的方式。

我想出的是以下两个功能。

typedef wchar_t TCHAR;

typedef struct {
    TCHAR *pDest;
    TCHAR *pLast;
    int    destLen;
} RflCatStruct;

RflCatStruct RflCatFilePathX(const TCHAR *pPath, RflCatStruct x)
{
    TCHAR *pDest = x.pLast;
    if (pDest && pPath) for ( ; x.destLen > 0 && (*pDest++ = *pPath++); x.destLen--)  x.pLast = pDest;
    return x;
}

RflCatStruct RflCatFilePathY(TCHAR *buffDest, int nLen, const TCHAR *pPath)
{
    RflCatStruct  x = { 0 };

    TCHAR *pDest = x.pDest = buffDest;
    x.pLast = buffDest;
    x.destLen = nLen;

    if (buffDest && nLen > 0) {   // ensure there is room for at least one character.
        *pDest = 0;   // ensure empty string if no path data provided.
        if (pPath) for (pDest = x.pLast; x.destLen > 0 && (*pDest++ = *pPath++); x.destLen--)  x.pLast = pDest;
    }
    return x;
}

使用这两个功能的示例如下。具有这两个函数的代码似乎可以与Visual Studio 2013一起使用。

TCHAR buffDest[512] = { 0 };
TCHAR *pPath = L"C:\\flashdisk\\ncr\\database";
TCHAR *pPath2 = L"\\";
TCHAR *pFilename = L"filename.ext";

RflCatFilePathX(pFilename, RflCatFilePathX(pPath2, RflCatFilePathY(buffDest, 512, pPath)));
printf("dest t = \"%S\"\n", buffDest);


printf("dest t = \"%S\"\n", RflCatFilePathX(pFilename, RflCatFilePathX(pPath2, RflCatFilePathY(buffDest, 512, pFilename))).pDest);


RflCatStruct  dStr = RflCatFilePathX(pPath2, RflCatFilePathY(buffDest, 512, pPath));
//   other stuff then
printf("dest t = \"%S\"\n", RflCatFilePathX(pFilename, dStr).pDest);

1 个答案:

答案 0 :(得分:1)

在调用函数之前,完全评估函数调用的参数。因此,对RflCatFilePath*的调用将按预期顺序进行评估。 (这由§6.5.2.2/ 10保证:“在评估函数指示符和实际参数之后但在实际调用之前有一个序列点。”)

如评论所示,snprintf功能可能是此问题的更好选择。 (asprintf会更好,并且有一个免费提供的垫片适用于Windows。)snprintf的唯一问题是你可能需要调用它两次。如果返回值不小于缓冲区的大小,它总是返回缓冲区中存储的 的字节数,因此需要分配更大的空间缓冲区(您现在知道它的大小)并再次调用snprintf

asprintf为您做到这一点,但它是标准库的BSD / Gnu扩展。

在连接文件路径的情况下,操作系统/文件系统支持最大字符串长度,您应该能够找到它的内容(尽管在非Posix系统上可能需要特定于操作系统的调用) )。因此,如果连接不适合512字节缓冲区,则简单地返回错误指示可能是合理的。

为了好玩,我加入了一个递归的varargs连接器:

#include <stdarg.h>
#include <stdlib.h>
#include <string.h>

static char* concat_helper(size_t accum, char* chunk, va_list ap) {
  if (chunk) {
    size_t chunklen = strlen(chunk);
    char* next_chunk = va_arg(ap, char*);
    char* retval = concat_helper(accum + chunklen, next_chunk, ap);
    memcpy(retval + accum, chunk, chunklen);
    return retval;
  } else {
    char* retval = malloc(accum + 1);
    retval[accum] = 0;
    return retval;
  }
}
char* concat_list(char* chunk, ...) {
    va_list ap;
    va_start(ap, chunk);
    char* retval = concat_helper(0, chunk, ap);
    va_end(ap);
    return retval;
}

由于concat_list是一个varargs函数,因此需要在参数末尾提供(char*)NULL。另一方面,您不需要为每个新参数重复函数名称。所以一个示例调用可能是:

concat_list(pPath, pPath2, pFilename, (char*)0);

(我想你需要一个wchar_t*版本,但更改应该是显而易见的。请注意malloc。)出于生产目的,递归应该可以被迭代版本替换,该版本遍历参数列表两次(参见va_copy)但我总是喜欢“后退”递归模式。