V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
shijingshijing
V2EX  ›  程序员

main 函数的 argv 参数用 char* argv[ ]还是 char** argv 合适?

  •  
  •   shijingshijing · 2019-07-08 10:33:43 +08:00 · 4161 次点击
    这是一个创建于 1958 天前的主题,其中的信息可能已经有所发展或是发生改变。

    这个问题其实以前没怎么注意,反正都是指向字符串的指针,但是最近看几个底层库的实现,基本上都是用的

    int main(int argc, char** argv)
    

    这种形式进行声明

    以前没怎么关注过这个细节,写过

    int main(int argc, char* argv[])
    

    好像也经常有

    int main(void)
    

    甚至

    void main(void)
    

    阿里巴巴的 Java 开发手册里面第 7 条也提到了强制使用 String[] args 而否定了 String args[]这种形式。

    Visual Studio 新建项目自动生成的代码也是使用了 string[] args。

    想了解一下,这个里面究竟有什么讲究?如果是 c 和 c++的话,是不是一定写成 char** argv 更好?

    33 条回复    2019-07-09 14:45:22 +08:00
    andrewhxism
        1
    andrewhxism  
       2019-07-08 10:41:16 +08:00
    语法上的区别,等效于没区别
    maxco292
        2
    maxco292  
       2019-07-08 10:43:28 +08:00 via Android
    void main 是 c 式语法,不推荐,其他没啥区别
    PTLin
        3
    PTLin  
       2019-07-08 10:50:27 +08:00
    UNIX 风格是 int main(int argc, char *argv[])和 int main(void),其实还有一种 int main(int argc, char *argv[], char *envp[]),其中第三个参数是环境表地址,我感觉还是 char *argv[]更符合直觉。
    shijingshijing
        4
    shijingshijing  
    OP
       2019-07-08 10:51:42 +08:00
    @maxco292
    恩,这个我知道,我忘记在哪本书里看到过了,好像说的是其实 C 还是 UNIX,自始至终都没有强制要求 int 返回值,也没有要求一定要有 argc 和 argv 参数。(具体记不太清楚了)我不是纠结 int main(void)和 void main(void)这两种。

    @andrewhxism
    我的意思是,为什么阿里要求强制用 String[] args 而否定 String args[],如果刚好项目里面用到了一个第三方提供的源代码库,里面都是 String args[],他们会怎么处理。。。
    这个要求只是简单的为了避免引入书写错误而规定的?还是有其他性能或者质量方面的考量?
    haozhang
        5
    haozhang  
       2019-07-08 11:05:08 +08:00 via Android
    char *argv[]更好,明确表明是 char *数组。
    ejq
        6
    ejq  
       2019-07-08 11:05:58 +08:00 via Android   ❤️ 1
    @shijingshijing String[] 是 Java 风格
    gunavy
        7
    gunavy  
       2019-07-08 11:46:15 +08:00
    辛亏你不做 js,要不你的整个人生都是纠结的😂
    forcecharlie
        8
    forcecharlie  
       2019-07-08 11:59:22 +08:00
    其实无所谓,无论哪一种形式的 main 编译后的函数名依然是 main,这是 C Style 的,无论采用什么形式的 main,编译时,链接器会帮你自动将将进程的入口点链接到 main。如果你不需要命令行参数,可以使用 `int main()`,如果你需要修改 命令行参数,可以使用 `int main(int argc,char **argv)`(这通常在 Linux 系统修改 ps 进程名。),否则可以使用 `int main(int argc, char* argv[])`。envp 需要则使用,不需要不使用。

    https://github.com/bminor/musl/blob/65c8be380431eebe4d70d130bd38563f8df9a7d7/src/env/__libc_start_main.c

    https://github.com/bminor/musl/blob/master/crt/rcrt1.c
    ipwx
        9
    ipwx  
       2019-07-08 12:03:52 +08:00   ❤️ 1
    老哥,C++ 没有 string[] argv 这种用法。

    不同语言的代码风格,能参考嘛?
    Mithril
        10
    Mithril  
       2019-07-08 12:07:08 +08:00   ❤️ 1
    看你提到了 VS,大概应该是 Windows 程序了。
    Windows 程序的入口其实是个 int (*)(void),没有入口参数的。Windows 创建进程以后调用 PE 入口就没有参数。
    然后你的 CRT 会初始化,它会用 GetCommandline 一类的 Windows API 从系统获取 PEB 里面保存的命令行参数,然后构造出一块内存保存这些东西,再用这玩意作为你的 main 函数的参数,调用你的 main。
    CRT 传给你的是个 char **,你愿意转成[]随你便的。
    包括 Java 那个也是一样,所有写成 main 的东西都是由 Runtime 调用的,Runtime 设计成什么样传进去的参数就是什么样了。
    GeruzoniAnsasu
        11
    GeruzoniAnsasu  
       2019-07-08 12:23:32 +08:00 via Android   ❤️ 1
    这个现象是这样的
    几乎所有的 ide 自动生成 main 函数用的都是 char *argv[]这种形式

    但不翻一下文档你一般很难想起来*和[]哪个优先级高,要不要写括号。所以手写开头一般 char**,不会错,也不用考虑括号



    破案:你你看到的那些底层库估计是 vim 写的,没有 ide 自动生成 main
    vkhsyj
        12
    vkhsyj  
       2019-07-08 12:26:16 +08:00
    两个是一样的,两个都是通俗写法,个人觉得 char* argv[] 语义更加明确
    codehz
        13
    codehz  
       2019-07-08 12:53:05 +08:00
    @Mithril #10 linux 也是这样(入口点是 crt 的函数,准备好各种参数后然后再调用 main (不然你试试强行改入口点到 main 会不会崩(
    Mithril
        14
    Mithril  
       2019-07-08 13:33:46 +08:00
    @codehz 我的意思是 Windows 的 PE 入口是个 int (*)(void),linux 不太清楚是不是。但是 CRT 这些东西都是一致的。
    shijingshijing
        15
    shijingshijing  
    OP
       2019-07-08 15:53:25 +08:00
    @ipwx
    我说 Visual Studio 明显是说 C#啊,你在 Visual Studio 里面新建 C++工程,VS 自动给你生成的 boilerplate 是这种:
    ```
    int main(array<System::String ^> ^args)
    ```

    我现在其实最想知道的是为什么阿里 Java 开发手册里规定强制使用 String[] args 且否定了 String args[],比较好奇原因。
    wdv2ly
        16
    wdv2ly  
       2019-07-08 17:30:39 +08:00 via Android
    好奇,java 有第二种写法吗?反正 c#没有第二种写法
    jamesliu96
        17
    jamesliu96  
       2019-07-08 17:35:51 +08:00 via Android
    想开点😶
    smdbh
        18
    smdbh  
       2019-07-08 18:01:58 +08:00
    面向 memory 编程,就不纠结了
    geelaw
        19
    geelaw  
       2019-07-08 18:40:38 +08:00   ❤️ 1
    楼主问了好几个不同的问题。

    就 C/C++ 标准来说,正确的 main 的签名只有以下 2 种

    int main(void);
    int main(int, char **);

    注意,char *argv[] 和 char **argv 作为形参是完全一样的,以及使用 typedef 导致的等价定义也是允许的。就语言层面,没有什么讲究。

    就 Java 来说,一维数组形参可以用 TypeName argName[] 或者 TypeName[] argName 声明,它们是等价的写法。阿里巴巴的规范是选择他们内部喜欢的写法,原因可以理解为把类型名挤在一起便于理解。

    C# 中,一维数组的形参可以用 TypeName[] argName 声明,不能使用 TypeName argName[] 的原因是后者是 C# 中不存在的写法(不符合句法)。
    ipwx
        20
    ipwx  
       2019-07-08 19:00:56 +08:00
    @shijingshijing 首先,Java 不存在指针。C# safe 代码不存在指针。所以你的出发点就不一样。

    其次,<System::String^> 不是 C++ 项目,是 .NET C++ 项目,两者截然不同。
    shijingshijing
        21
    shijingshijing  
    OP
       2019-07-08 21:11:47 +08:00
    @smdbh 恩,我知道最后都是地址的操作。

    @geelaw
    说的很清楚了,谢谢。可以理解为阿里的这条标准纯粹是为了强迫症和项目管理、代码评审等目的而特意规定的,而不是除去性能、安全等其他目的,对吗?

    @ipwx 我从头到尾都没有说是因为指针什么的,把指针拿来作为出发点吧。我说的 visual studio 生成的代码没指定说是生成的 C++代码吧。

    不需要太多的说教,底层我也懂,现在是关注一下这些细节,而且上面我也强调了,特别感兴趣的是阿里为什么强制使用 String[] args,麻烦回复一点有意义的内容,不太喜欢你这种语气,也不喜欢你这样的回复风格,否则请 block 我以及本帖,谢谢。
    msg7086
        22
    msg7086  
       2019-07-08 22:12:38 +08:00 via Android   ❤️ 2
    这贴几乎可以当作提问的智慧的教科书般的反面教材了。
    标题问 C 的指针和数组,内容说 Java,顺便提到一个无关的 C#,最后再回过头否定自己标题的提问,顺便把回答者批判一番。

    还行。我佛了。
    shijingshijing
        23
    shijingshijing  
    OP
       2019-07-08 22:14:58 +08:00
    @msg7086

    请主动 block,不谢。
    msg7086
        24
    msg7086  
       2019-07-08 23:39:37 +08:00
    @shijingshijing 这么教科书式的帖子 Block 了多可惜。
    zhao4dick25cm
        25
    zhao4dick25cm  
       2019-07-09 07:34:18 +08:00 via iPhone
    没必要,少写一点是一点,就是 int main()
    jaskle
        26
    jaskle  
       2019-07-09 07:47:22 +08:00 via Android
    数组式好理解,c 这种东西,写法千万种
    Mithril
        27
    Mithril  
       2019-07-09 10:22:54 +08:00
    @shijingshijing Java 的话数组声明就是两种都支持的,这跟 Main 函数没关系。不管用那种写法,都只是说这参数是个数组而已。
    然而这两种写法比较容易混,比如
    String[] arr1, arr2[];
    arr2 是个 String[][]
    官方的说法是这个[]可以出现在声明的最前面,也可以出现在特定变量处。但不推荐两种写法混着写,所以一般代码规范就强制要求使用一种写法。
    这个其实不光会影响变量,比如你甚至可以把一个返回数组的方法写成这样:
    String method()[]{return new String[1];}
    String[] method()[]{return new String[1][1];}
    看起来就比较乱
    FrankHB
        28
    FrankHB  
       2019-07-09 11:53:14 +08:00
    允许 void main 还敢装做是 C 或者 C++的,除非是特定 C 的 freestanding implementation,就是扯蛋。
    注意 C 和 C++略有不同。
    cf. https://github.com/FrankHB/pl-docs/blob/master/zh-CN/main-function.md

    剩下的问题,主要来自 C-like 语法的不成熟的语法设计:
    https://github.com/FrankHB/pl-docs/blob/master/zh-CN/about-syntax.md#%E5%85%B3%E4%BA%8E%E5%8E%86%E5%8F%B2%E9%81%97%E7%95%99%E9%97%AE%E9%A2%98%E7%9A%84%E4%BE%8B%E5%AD%90

    如果允许,使用 string[] args 这样的风格是更推荐的做法。不过,C 和 C++中根本不允许这种语法……
    考虑到 char**本身倾向于造成理解上的含义混乱(凭啥不是 const char**或者 char* const*……,凭啥*表示数组而不是 in/out parameter )而且[]允许字面上更好的可读性(虽然不被语言支持,但起码你能自然地写[/*size=1~3*/]这样暗示源的预先设计的范围),个人建议 char* args[]而避免 char** args。
    类似 char**这样的写法,基本上在 C++不应该出现,因为没什么合适的理由使这种潜在有歧义的写法显得有必要。而 C 的 char**可以表达 C++的 char*&这样的情况,约定只有这种情况使用 char**,则用意相对是清楚的。
    FrankHB
        29
    FrankHB  
       2019-07-09 12:05:12 +08:00
    @geelaw 我不记得有哪个版本的 C/C++ 标准提出“正确的 main 的签名只有以下 2 种”(姑且搁置“签名”是指什么的问题)。请核实并指出来源。
    geelaw
        30
    geelaw  
       2019-07-09 12:40:10 +08:00
    @FrankHB #29 OK,更准确的说法是“在任何符合 C/C++ 标准的语言实现中总是正确的 main 的签名只有 2 种”,例如见 n4659 6.6.1.2 和 n1256 5.1.2.2.1.1。

    签名,如果不是一个 C/C++ 语言中的概念,就是日常理解的含义,是若干个类型的有序组(返回类型,第一个形参类型,第二个形参类型……)以及一个 bool (是否具有 ... 变长参数)。两个签名相同当且仅当这个 (有序组, bool) 的 pair 相等。
    FrankHB
        31
    FrankHB  
       2019-07-09 12:57:40 +08:00
    @geelaw 你的说法是错的。之前我给的链接有提供原文引用和分析(新的标准修订版本原则上不会改动这里的内容):
    github.com/FrankHB/pl-docs/blob/master/zh-CN/main-function.md
    首先的具体问题:你提的“正确”事实上对不上标准中的和一般理解的“正确性”相关的要求(不管是形式语法、以 shall 明确的条款、constraint 还是 well-definedness ),不管是对 conforming implementation 还是对 program 来说。
    标准对 conforming implementation 要求需要支持两种的 main 声明,这不表示禁止其它任意的扩展(也不直接禁止程序使用这样的扩展)。例如,ISO C++明确禁止非 int 返回类型的全局 main 函数,但没对参数类型有同样的限制。
    你的另外的一个技术错误是无视了 C 和 C++的不同。C 的 int main(void);原型声明对应 C++的 int main();,但 C 的 int main()作为声明并不等同于 int main(void);,在 C++中近似 int main(...);。
    函数的签名(signature) 在 ISO C++ 中一直有明确定义(用于支持重载等),不同版本的定义有差别但都排除了返回类型,参见[defns.signature]。
    ISO C 没有明确对应的“签名”概念。日常所谓的“签名”,语源是数理逻辑,一般是指“函数类型”,注意这和上面的 C++中的定义有差别——它依赖返回类型(而 C 和 C++ 其实也是有这样的“函数类型”的概念的)。在 C 从 C++ 的设计中照搬来原型声明这个设计之前,“签名”的含义是不怎么明确的。特别地,C 仍支持 () 参数列表这样明确不指定参数类型的情况。所以虽然你说的“日常理解的含义”虽然不算有很大的普遍问题,但这个上下文中反而会引起混乱。
    geelaw
        32
    geelaw  
       2019-07-09 13:15:22 +08:00
    @FrankHB #31

    “在任何符合 C/C++ 标准的语言实现中总是正确”,我已经加上了全称量词,那么我应该说“在任何……总是被支持”。显然可以造出一个只支持 int main(void) 和 int main(int, char **) 的实现,所以这是惟二“总是正确”(总是被支持)的写法。

    我觉得你是知道我知道 C/C++ 对于 () 作为形参列表的不同的 - - 我没有指出 C/C++ 在这方面的区别,是因为我的写法在 C/C++ 中含义一样。( C++ 仍然支持 (void) 表达 () 见于 n4659 11.3.5.4。)

    关于签名,我并不知道文档中使用的定义,是否包含返回类型只是一个取决于使用目的的美学选择。不过感谢你指出标准里的定义是什么样的。

    最后,我并不知道 C 支持(意思是“任何实现必须支持”而不是“允许这样的扩展”) int main()。
    FrankHB
        33
    FrankHB  
       2019-07-09 14:45:22 +08:00
    @geelaw 就“在任何……总是被支持”,指的若是程序(strictly conforming program),且限定是 hosted implementation,则你的补充是对的;除了 ISO C 在 Program startup 一节中对 main 有显式允许“ or equivalence ”的说法(其中一个例子就是 argv 的**和[]的等价性,见脚注)这点外。
    int main(); 不是任何 ISO C 实现都被要求支持的声明,除非同时存在被要求支持的 main 的定义之一的原型声明。此时,前者是和定义兼容(compatible)的函数声明。
    但若 int main()出现在函数定义中,这仍然是要求被支持的,因为 ISO C 的说法是 defined ... with no parameters ... or equivalence,并没说此处的声明必须要有原型或者必须是明确的(void)(甚至没说是 parameter type list )——尽管函数声明符中的()是 obsolescent feature ——所以即便排除原型保证的 diagnostic 的好处,定义中写成 int main(void)仍然更好。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2879 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 08:43 · PVG 16:43 · LAX 00:43 · JFK 03:43
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.