我有一个简单的c ++程序(和c类似的程序)只打印出第一个参数
#include <iostream>
int main(int argc, char** argv)
{
if(argc > 1)
std::cout << ">>" << argv[1] << "<<\n";
}
我可以传递二进制数据(我已尝试过bash)作为参数,如
$./a.out $(printf "1\x0123")
>>1?23<<
如果我尝试传递空值,我会得到
./a.out $(printf "1\x0023")
bash: warning: command substitution: ignored null byte in input
>>123<<
显然bash(?)不允许这个
但是可以通过这种方式发送null作为命令行参数吗? c或c ++是否对此有任何限制?
编辑:我不是在日常的c ++中使用它,这个问题只是出于好奇
答案 0 :(得分:1)
这个答案是用C语言编写的,但可以编译为C ++,并且在两者中都是一样的。我引用C11标准; C++ standards中有相同的定义。
<强> C11 §5.1.2.2.1 Program startup 强>
如果argc
的值大于零,则数组成员argv[0]
到argv[argc-1]
包含指向字符串的指针,在程序启动之前由主机环境给出实现定义的值<强> C11 §7.1.1 Definitions of terms 强>
字符串是由第一个空字符终止并包含第一个空字符的连续字符序列。
这意味着传递给main()
中argv
的每个参数都是以空字符结尾的字符串。在字符串末尾的空字节之后没有可靠的数据 - 搜索将访问超出字符串的范围。
因此,正如在问题的注释中详细说明的那样,在正常的事件过程中不可能通过参数列表向程序获取空字节,因为空字节被解释为每个参数的结尾。
这并没有留下太多的蠕动空间。但是,如果调用/调用程序和被调用/调用程序都同意约定,那么,即使有标准规定的限制,您也可以将任意二进制数据(包括任意空字节序列)传递给调用程序 - 达到实施所强加的参数列表长度的限制。
惯例必须遵守:
argv[0]
除外,被忽略,最后一个参数argv[argc-1]
)由非空字节流后跟空值组成。这可能导致一个程序,如(null19.c
):
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static void hex_dump(const char *tag, size_t size, const char *buffer);
int main(int argc, char **argv)
{
if (argc < 2)
{
fprintf(stderr, "Usage: %s arg1 [arg2 '' arg4 ...]\n", argv[0]);
exit(EXIT_FAILURE);
}
size_t len_args = 0;
for (int i = 1; i < argc; i++)
len_args += strlen(argv[i]) + 1;
char buffer[len_args];
size_t offset = 0;
for (int i = 1; i < argc; i++)
{
size_t arglen = strlen(argv[i]) + 1;
memmove(buffer + offset, argv[i], strlen(argv[i]) + 1);
offset += arglen;
}
assert(offset != 0);
offset--;
hex_dump("Argument list", offset, buffer);
return 0;
}
static inline size_t min_size(size_t x, size_t y) { return (x < y) ? x : y; }
static void hex_dump(const char *tag, size_t size, const char *buffer)
{
printf("%s (%zu):\n", tag, size);
size_t offset = 0;
while (size != 0)
{
printf("0x%.4zX:", offset);
size_t count = min_size(16, size);
for (size_t i = 0; i < count; i++)
printf(" %.2X", buffer[offset + i] & 0xFF);
putchar('\n');
size -= count;
offset += count;
}
}
可以使用以下方法调用:
$ ./null19 '1234' '5678' '' '' '' '' 'def0' ''
Argument list (19):
0x0000: 31 32 33 34 00 35 36 37 38 00 00 00 00 00 64 65
0x0010: 66 30 00
$
第一个参数被认为由5个字节组成 - 四个数字和一个空字节。第二个是类似的。第三个到第六个参数每个代表一个空字节(如果你需要大量连续的空字节会很痛苦),然后还有另一个五个字节的字符串(三个字母,一个数字,一个空字节)。最后一个参数为空,但确保最后有一个空字节。如果省略,则输出将不包括该最终终端空字节。
$ ./null19 '1234' '5678' '' '' '' '' 'def0'
Argument list (18):
0x0000: 31 32 33 34 00 35 36 37 38 00 00 00 00 00 64 65
0x0010: 66 30
$
除了数据中没有尾随空字节外,这与之前相同。问题中的两个例子很容易处理:
$ ./null19 $(printf "1\x0123")
Argument list (4):
0x0000: 31 01 32 33
$ ./null19 1 23
Argument list (4):
0x0000: 31 00 32 33
$
这严格遵循标准,假设只有空字符串被识别为有效参数。实际上,这些参数在内存中已经是连续的,因此在许多平台上都可以避免将复制阶段放入缓冲区。但是,该标准没有规定参数字符串在内存中是连续布局的。
如果您需要具有二进制数据的多个参数,则可以修改约定。例如,您可以获取字符串的控制参数,该参数指示有多少后续物理参数构成一个逻辑二进制参数。
所有这些都依赖于按照约定解释参数列表的程序。这不是一般的解决方案。