这个缓冲区为什么会被填满?

14

在此之前,我为这个问题的无用标题道歉,但似乎没有更合适的。

这里的想法是复制 argv 到另一个变量中,实质上是创建一个副本。因此,函数的基本思路是使用 malloc() 来请求一些空间进行复制,然后遍历 argv 并复制每个元素。

这是我正在使用的代码,目前的开发环境是 Visual Studio 2019(即使它不是严格的 C 编译器...):

// Returns a copy of an array of strings (intended for argv, but should work with any of them):
wchar_t** copyArgv(size_t argc, wchar_t* argv[]) {
    // Allocate space for the array of arguments:
    wchar_t** argsCopy = malloc(((argc + 1) * sizeof(wchar_t*)));
    if (!argsCopy)
        return NULL;
    // Copy each one of them:
    for (size_t i = 0; i < argc; i++) {
        argsCopy[i] = _wcsdup(argv[i]);
        if (!argsCopy[i]) {
            // Should also free any previous copied string I left that part out in the paste.
            free(argsCopy);
            return NULL;
        }
    }
    argsCopy[argc] = NULL;
    return argsCopy;
}

我一直在尝试不同的方法来复制argv,但每一种方法都让VS认为在复制参数时可能会发生缓冲区溢出(行:argsCopy[i] = _wcsdup(argv[i]);),或者在下一行读取无效数据,也就是读取超出保留空间的边界。

这一切都让我相信问题出在(现在)仅有的malloc()调用上,用于为参数数组保留空间。

然而,我一直在努力找出问题所在,我是说,我认为我正在请求足够的空间。

我也尝试了其他编译器,最新稳定版本的Clang和GCC似乎没有显示任何此类警告。因此,我决定向您这些经验丰富的程序员请教,看看您是否能发现问题,或者它是某种编译器错误(我不太可能)。

对于参考,这些是VS2019在64位编译中引发的确切警告:

在赋值操作中:

写入'argsCopy'时的缓冲区溢出:可写大小为“((argc+1))*sizeof(wchar_t *)”字节,但可能写入“16”个字节。

下一行,对NULL的测试:

从'argsCopy'中读取无效数据:可读大小为“((argc+1))*sizeof(wchar_t *)”字节,但可能读取“16”个字节。


2
你忘记初始化最后一个数组元素了:argsCopy[argc] = NULL - gudok
1
我认为这两行代码并没有引起警告,没有问题。 - Barmar
不用在意我说的话。确实提供了一个终止NULL。但是你的复制可能不需要它。 - ikegami
1
命令行参数长度未知。在复制数据之前,您可以检查每个字符串的前“MAX”个字符中是否有“\0”。至于16字节的事情是从哪里来的,我不知道。 - Lundin
1
我在谈论字符串本身。与此无关,你的main()格式不是标准的。此外,程序员不能决定 main() 的格式,只能使用编译器指定的格式。 - Lundin
显示剩余9条评论
4个回答

1

这些是静态分析器的警告。例如,它试图识别缓冲区溢出情况。

警告

需要注意的是,这些只是警告而不是错误消息。编译器表示可能存在一些潜在问题。静态分析通常是一件困难的事情。

误报

没有缓冲区溢出情况,因此这是一个误报。我会假设这条消息在未来的更新中会消失。

稍作修改

如果我们将内存分配行更改如下:

wchar_t** argsCopy = (wchar_t**)calloc(argc + 1, sizeof(wchar_t*));

那么Visual Studio 2019将不再出现任何警告。

分配的字节数保持不变,但是警告消息消失了。

测试

更改之前,VS错误列表如下:

before

在我提出的更改应用后,警告已经消失:

after


2
在将 void* 转换为另一种指针类型时不需要进行强制转换(在 C++ 中需要,但本问题标记为 C)。 - aschepler
1
你说得对。在这种情况下,我的个人偏好是使用显式转换 - 即使在 C 中也是如此。在 C++ 中甚至是必需的。我想这只是个人口味问题 :-) - Stephan Schlecht
在https://dev59.com/dHRB5IYBdhLWcg3wgHWr上讨论了许多利弊(带有不同的结论)。 - aschepler
我理解你的观点,这被视为争议。关于隐式函数:现代C编译器不应该有问题。无论如何,我将从答案中删除额外的提示 - 它与实际问题或答案没有直接关系。 - Stephan Schlecht
请注意,calloc 会将分配的内存清零,因此不需要显式地将最后一个指针设置为 NULL。此外,还有一点额外的开销需要清除将要初始化的内存,但在我能想到的任何使用情况下,这应该是无关紧要的。 - hyde
显示剩余2条评论

0
关键点可能是您没有为要复制的数据分配足够的空间。
我不知道我是否真正理解了你想要做什么,我假设你想要将二维字符数组复制到另一个内存段,并返回其地址,数组有'argc'行,每行字符串的地址存储在argv数组中。
但是,为什么您使用argc+1而不是argc? 为了使用malloc额外的空间以防止缓冲区溢出吗? 更重要的是,sizeof(wchar_t*)将返回指针的大小(64位系统中为8个字节),它不会返回我们想要的二维数组中任何一个字符串的大小。

0

我可能错了,但是在尝试使用 Visual Studio 的在线副本(https://rextester.com/l/c_online_compiler_visual)后,我不得不假设您已经忘记包含 string.hwchar.h(两者都可以)。Visual Studio似乎假定您的返回类型是整数,而不是wchar_t *,因为该函数未定义。由于这是以“_”开头的保留函数,因此似乎有一些“神奇”的事情发生,因此它没有发出其他警告?然而,由于缺乏确切的环境信息,我被迫做出部分推断(您对目标更改警告的评论给了我一个希望正确的提示)。


-2

1) 复制 argv 的一种方法如下所述,但是。 2) 我无法理解为什么您想要复制 argv?它解决了哪些用例/用户问题?

正如我在(1)中提到的,这是其中一种方法,本质上是将 argv 中的所有内容复制到您的缓冲区中。它大致是这样的(附言:由于我在出租车上打字,因此没有访问高质量的 C 编译器进行交叉检查,所以可能会有编译错误)

int numArgc = argc
char** argvCopy;

for (i=0;i<argc,i++)
{

 argvCopy[i] = malloc(sizeof(char)*strlen(argv[i]));
 strcpy(argvCopy[i], argv[i]);

}

//please do not forget to Free this malloc'ed memory (a very common C programming error) //when you don't need it anymore 

请告诉我您想要解决的问题


1
我不相信你这里的代码会起作用 - 因为argvCopy没有初始化,所以argvCopy[i] = ...这一行会导致未定义的行为。 - templatetypedef

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接