代码之家  ›  专栏  ›  技术社区  ›  Iwillnotexist Idonotexist

ISO C允许提供给主体()的Agv[]指针的混叠吗?

  •  17
  • Iwillnotexist Idonotexist  · 技术社区  · 6 年前

    iso c要求宿主实现调用名为 main 是的。如果程序接收到参数,它们将作为 char* 指针,中的第二个参数 主要的 的定义 int main(int argc, char* argv[]) .

    iso c还要求 argv 数组是可修改的。

    但是 阿格夫 彼此化名?换句话说,是否存在 i , j 如此

    • 0 >= i && i < argc
    • 0 >= j && j < argc
    • i != j
    • 0 < strlen(argv[i])
    • strlen(argv[i]) <= strlen(argv[j])
    • argv[i] 别名 argv[j]

    在程序启动时?如果是的话,直接写 argv[i][0] 也可以通过混叠字符串看到 阿格夫 是的。

    ISOC标准的相关条款如下,但不允许我最终回答这个名义问题。

    5.1.2.2.1程序启动

    在程序启动时调用的函数命名为 主要的 是的。实现声明此函数没有原型。它应定义为返回类型 int 没有参数:

    int main(void) { /* ... */ }
    

    或具有两个参数(这里称为 argc 阿格夫 ,尽管可以使用任何名称,因为它们对于声明它们的函数是局部的:

    int main(int argc, char *argv[]) { /* ... */ }
    

    或同等的;10)或以某种其他实现定义的方式。

    如果它们被声明,则 主要的 功能应遵守以下约束:

    • 价值 argc公司 应该是非负的。
    • argv[argc] 应为空指针。
    • 如果 argc公司 大于零,数组成员 argv[0] 通过 argv[argc-1] 包容性应包含指向字符串的指针,这些指针在程序启动之前由宿主环境给出实现定义的值。目的是向在托管环境中的其他程序启动之前确定的程序信息提供。如果主机环境不能同时提供大写字母和小写字母的字符串,则该实现将确保字符串以小写方式接收。
    • 如果值 argc公司 大于零的字符串 参数[0] 表示程序名; argv[0][0] 如果主机环境中没有程序名,则应为空字符。如果 argc公司 大于1,字符串指向 argv[1] 通过 argv[argc-1] 表示程序参数。
    • 参数 argc公司 阿格夫 以及 阿格夫 数组应该由程序修改,并保留它们在程序启动和程序终止之间的最后存储值。

    据我所读,对这一名义问题的回答是“是”,因为没有任何地方是明确禁止的,也没有任何地方是标准敦促或要求使用 char* restrict* -合格的 阿格夫 但答案可能是解释 “并在程序启动和程序终止之间保留它们最后存储的值。” 是的。

    这个问题的实际含义是,如果答案是“是”,则希望修改字符串的便携程序。 阿格夫 必须首先执行(相当于)posix strdup() 为了安全。

    3 回复  |  直到 6 年前
        1
  •  9
  •   too honest for this site    6 年前

    根据我的阅读,对这个名词的回答是“是”,因为它不在任何地方。 明令禁止,标准没有要求 使用restrict限定的argv,但答案可能会打开 “解释”并保留它们在程序之间的最后存储值 启动和程序终止。

    我同意标准并没有明确禁止参数向量的元素成为彼此的别名。我不认为可修改性和价值保留条款与这一立场相矛盾,但它们确实向我表明,委员会没有考虑使用别名的可能性。

    这个问题的实际意义是如果答案是 确实,“是”,一个希望修改字符串的可移植程序。 argv必须首先对它们执行(相当于)posix strdup(),以便 安全。

    事实上,这正是我认为委员会甚至没有考虑这种可能性的原因。如果他们这样做了,那么他们肯定至少会包含一个脚注,以达到同样的效果,或者明确指定参数字符串都是不同的。

    我倾向于认为这个细节避免了委员会的注意,因为在实践中,实现确实提供了不同的字符串,而且因为程序修改它们的参数字符串(尽管修改)是很少见的。 argv 它本身比较常见)。如果委员会同意在这一领域发表官方解释,那么我不会感到惊讶,因为他们可能会抵制别名。

    但是,除非发布了此类解释,否则严格的一致性不允许您依赖 先验的 阿格夫 元素没有被别名。

        2
  •  6
  •   John Zwinck    6 年前

    它在常见的*nix平台(包括linux和mac操作系统,可能还有freebsd)上的工作方式是 argv 是一个指针数组,它包含一个接一个的参数字符串(只由null终止符分隔)。使用 execl() 不会改变这一点--即使调用方多次传递同一指针,也会多次复制源字符串,对于相同(即别名)指针没有特殊行为(这种情况很少见,优化起来没有太大好处)。

    但是,c不需要这种实现。真正的偏执狂可能想在修改字符串之前复制每个字符串,如果内存有限且循环过多,则可能跳过复制。 阿格夫 显示没有指针实际别名(至少在程序要修改的指针中)。除非你在开发飞行软件之类的东西,否则这看起来太偏执了。

        3
  •  2
  •   Steve Summit    6 年前

    作为一个数据点,我在几个系统上编译和运行以下程序。(免责声明:这些程序旨在提供一个数据点,但正如我们所看到的,它们是这样做的。 最后按说明回答问题。)

    p1.c 以下内容:

    #include <stdio.h>
    #include <unistd.h>
    
    int main()
    {
        char test[] = "test";
        execl("./p2", "p2", test, test, NULL);
    }
    

    p2.c :

    #include <stdio.h>
    
    int main(int argc, char **argv)
    {
        int i;
        for(i = 1; i < argc; i++) printf("%s ", argv[i]); printf("\n");
        argv[1][0] = 'b';
        for(i = 1; i < argc; i++) printf("%s ", argv[i]); printf("\n");
    }
    

    我尝试过的每一个地方(在macos和多种unix和linux下)都会打印

    test test 
    best test 
    

    因为第二行从来没有 best best “这证明,在测试的系统中,在运行第二个程序时,字符串不再混叠。

    当然,这个测试 证明字符串 argv 在任何情况下,在任何系统中都不能使用别名。我想所有证明的是,毫不奇怪,每一个被测试的操作系统在时间之间至少会重复一次参数列表。 p1 电话 execl 那时候 p2 实际上是被调用的。换句话说,由调用程序构造的参数向量是 直接在被调用程序中使用,并且在复制过程中,它(又一点也不奇怪)“归一化”,意味着任何混叠的效果都会丢失。

    (我说这并不奇怪,因为如果你想想 exec 系统调用族实际上是工作的,进程内存的方式是在UNIX类系统下进行的,调用程序的参数列表是不可能直接使用的; 至少要复制一次,进入新的执行进程的地址空间。此外,任何明显的和直接的复制参数列表的方法总是自动地以这种方式“正常化”;内核必须做显著的、额外的、完全不必要的工作,以便检测和保存任何混叠。

    为了以防万一,我修改了第一个程序:

    #include <stdio.h>
    #include <unistd.h>
    
    int main()
    {
        char test[] = "test";
        char *argv[] = {"p2", test, test, NULL};
        execv("./p2", argv);
    }
    

    结果没有变化。


    所有这些都表明,我同意这个问题看起来像是标准中的疏忽或小事。我不知道有任何条款可以保证字符串是由 阿格夫 是不同的,这意味着一个未经授权的书面程序可能不依赖于这样的保证,不管它有多大可能(如这个答案所示),任何合理的实现都有可能做到这一点。