main 函数的 argv 参数用 char* argv[ ]还是 char** argv 合适? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
请不要在回答技术问题时复制粘贴 AI 生成的内容
shijingshijing
V2EX    程序员

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

  •  
  •   shijingshijing 2019 年 7 月 8 日 4907 次点击
    这是一个创建于 2439 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

    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 年 7 月 8 日
    语法上的区别,等效于没区别
    maxco292
        2
    maxco292  
       2019 年 7 月 8 日 via Android
    void main 是 c 式语法,不推荐,其他没啥区别
    PTLin
        3
    PTLin  
       2019 年 7 月 8 日
    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 年 7 月 8 日
    @maxco292
    恩,这个我知道,我忘记在哪本书里看到过了,好像说的是其实 C 还是 UNIX,自始至终都没有强制要求 int 返回值,也没有要求一定要有 argc 和 argv 参数。(具体记不太清楚了)我不是纠结 int main(void)和 void main(void)这两种。

    @andrewhxism
    我的意思是,为什么阿里要求强制用 String[] args 而否定 String args[],如果刚好项目里面用到了一个第三方提供的源代码库,里面都是 String args[],他们会怎么处理。。。
    这个要求只是简单的为了避免引入书写错误而规定的?还是有其他性能或者质量方面的考量?
    haozhang
        5
    haozhang  
       2019 年 7 月 8 日 via Android
    char *argv[]更好,明确表明是 char *数组。
    ejq
        6
    ejq  
       2019 年 7 月 8 日 via Android   1
    @shijingshijing String[] 是 Java 风格
    gunavy
        7
    gunavy  
       2019 年 7 月 8 日
    辛亏你不做 js,要不你的整个人生都是纠结的
    forcecharlie
        8
    forcecharlie  
       2019 年 7 月 8 日
    其实无所谓,无论哪一种形式的 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 年 7 月 8 日   1
    老哥,C++ 没有 string[] argv 这种用法。

    不同语言的代码风格,能参考嘛?
    Mithril
        10
    Mithril  
       2019 年 7 月 8 日   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 年 7 月 8 日 via Android   1
    这个现象是这样的
    几乎所有的 ide 自动生成 main 函数用的都是 char *argv[]这种形式

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



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

    我现在其实最想知道的是为什么阿里 Java 开发手册里规定强制使用 String[] args 且否定了 String args[],比较好奇原因。
    wdv2ly
        16
    wdv2ly  
       2019 年 7 月 8 日 via Android
    好奇,java 有第二种写法吗?反正 c#没有第二种写法
    jamesliu96
        17
    jamesliu96  
       2019 年 7 月 8 日 via Android
    想开点
    smdbh
        18
    smdbh  
       2019 年 7 月 8 日
    面向 memory 编程,就不纠结了
    geelaw
        19
    geelaw  
       2019 年 7 月 8 日   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 年 7 月 8 日
    @shijingshijing 首先,Java 不存在指针。C# safe 代码不存在指针。所以你的出发点就不一样。

    其次,<System::String^> 不是 C++ 项目,是 .NET C++ 项目,两者截然不同。
    shijingshijing
        21
    shijingshijing  
    OP
       2019 年 7 月 8 日
    @smdbh 恩,我知道最后都是地址的操作。

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

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

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

    还行。我佛了。
    shijingshijing
        23
    shijingshijing  
    OP
       2019 年 7 月 8 日
    @msg7086

    请主动 block,不谢。
    msg7086
        24
    msg7086  
       2019 年 7 月 8 日
    @shijingshijing 这么教科书式的帖子 Block 了多可惜。
    zhao4dick25cm
        25
    zhao4dick25cm  
       2019 年 7 月 9 日 via iPhone
    没必要,少写一点是一点,就是 int main()
    jaskle
        26
    jaskle  
       2019 年 7 月 9 日 via Android
    数组式好理解,c 这种东西,写法千万种
    Mithril
        27
    Mithril  
       2019 年 7 月 9 日
    @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 年 7 月 9 日
    允许 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 年 7 月 9 日
    @geelaw 我不记得有哪个版本的 C/C++ 标准提出“正确的 main 的签名只有以下 2 种”(姑且搁置“签名”是指什么的问题)。请核实并指出来源。
    geelaw
        30
    geelaw  
       2019 年 7 月 9 日
    @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 年 7 月 9 日
    @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 年 7 月 9 日
    @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 年 7 月 9 日
    @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     Solana     3836 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 29ms UTC 10:28 PVG 18:28 LAX 03:28 JFK 06:28
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86