在C++中使用execvp的最佳实践

11

一开始,我写了这样的东西

char* argv[] = { "ls", "-al", ..., (char*)NULL };
execvp("ls", argv);

然而,GCC弹出了这个警告:"C++禁止将字符串常量转换为char*。"

于是,我将我的代码改成了

const char* argv[] = { "ls", "-al", ..., (char*)NULL };
execvp("ls", argv);

结果,GCC 弹出这个错误:“从const char**转换为char* const*是无效的。”

于是,我修改了我的代码。

const char* argv[] = { "ls", "-al", ..., (char*)NULL };
execvp("ls", (char* const*)argv);

最终它可以正常工作,并且没有任何警告和错误,但我认为这有点繁琐,而且我在互联网上找不到类似的东西。

在C++中有更好的使用execvp的方法吗?


1
首先,终止符(char*)0应该是一个普通的0,或者最好是nullptr。至于你遇到的数组问题,我建议您阅读 execvp手册页。在那里,您会看到参数的类型,并可以定义该数组为该类型(开头加上一个const)。 - Some programmer dude
2
你调用的仍然是同一个函数。POSIX系统接口函数都是你调用的C函数。 - Some programmer dude
3
C和C++之间的一个区别是,在C中,字符串字面值并不真正是常量,而在C++中它们是常量。 - Some programmer dude
3
@MSalters 你应该考虑使用 execlp 函数。execvp 函数不是可变参数函数。即使使用 execlp 函数,最后一个“参数”也不应该显式地转换类型,最好使用 nullptr 关键字而不是 0 - Some programmer dude
2
@Someprogrammerdude:哎呀,是的。我看了一下我的代码,它确实使用了execl。而且,将nullptr_t传递给使用va_arg(arg, char*)的可变参数函数是UB。类型不匹配。在C++中,nullptr的目的是将其转换为目标类型的空指针,但C可变参数函数调用表达式没有目标类型。 - MSalters
显示剩余3条评论
3个回答

8

您遇到了一个真正的问题,因为我们面临着两个不兼容的限制:

  1. One from the C++ standard requiring you that you must use const char*:

    In C, string literals are of type char[], and can be assigned directly to a (non-const) char*. C++03 allowed it as well (but deprecated it, as literals are const in C++). C++11 no longer allows such assignments without a cast.

  2. The other from the legacy C function prototype that requires an array of (non-const) char*:

    int execv(const char *path, char *const argv[]);
    

因此,必须有一个const_cast<>,而我找到的唯一解决方案是包装execvp函数。

这里是一个完整的C ++演示解决方案。不便之处在于您需要编写一些粘合代码,但好处是您将获得更安全和更干净的C ++11代码(最终的nullptr已经被检查)。

#include <cassert>
#include <unistd.h>

template <std::size_t N>
int execvp(const char* file, const char* const (&argv)[N])
{
  assert((N > 0) && (argv[N - 1] == nullptr));

  return execvp(file, const_cast<char* const*>(argv));
}

int main()
{
  const char* const argv[] = {"-al", nullptr};
  execvp("ls", argv);
}

您可以使用以下命令编译此演示:
g++ -std=c++11 demo.cpp 

您可以在CPP参考中查看std::experimental::to_array的示例,其中采用了类似的方法。

4
这是execvp()声明和C++解释字符串字面值为常量char数组之间的冲突。由于向后兼容性,execvp()不能保证不修改其参数。
如果您担心强制类型转换,剩下的选择是复制参数列表,如下所示:
#include <unistd.h>
#include <cstring>
#include <memory>
int execvp(const char *file, const char *const argv[])
{
    std::size_t argc = 0;
    std::size_t len = 0;

    /* measure the inputs */
    for (auto *p = argv;  *p;  ++p) {
        ++argc;
        len += std::strlen(*p) + 1;
    }
    /* allocate copies */
    auto const arg_string = std::make_unique<char[]>(len);
    auto const args = std::make_unique<char*[]>(argc+1);
    /* copy the inputs */
    len = 0;                    // re-use for position in arg_string
    for (auto i = 0u;  i < argc;  ++i) {
        len += std::strlen(args[i] = std::strcpy(&arg_string[len], argv[i]))
            + 1; /* advance to one AFTER the nul */
    }
    args[argc] = nullptr;
    return execvp(file, args.get());
}

您可能会认为std::unique_ptr有些过头,但是这个函数在execvp()失败时能够正确清理,且该函数的返回值也是正确的。

演示:

int main()
{
    const char *argv[] = { "printf", "%s\n", "one", "two", "three", nullptr };
    return execvp("printf", argv);
}

one
two
three

或者,考虑使用 std::vector 实例来代替 arg_stringargs,并将 args.data() 传递给 execvp() - Toby Speight

0

execvpe需要一个char *const argv[]作为它的第二个参数。也就是说,它需要一个指向非常量数据的常量指针列表。在C中,字符串字面值是常量,因此会出现警告和将argv强制转换为char* const*的问题,这是一种hack方法,因为execvp现在可以写入argv中的字符串。我看到的解决方案是为每个项目分配一个可写缓冲区,或者只是使用execlp,它可以使用const char*参数,并允许传递字符串字面值。


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