这个问题其实以前没怎么注意,反正都是指向字符串的指针,但是最近看几个底层库的实现,基本上都是用的
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 更好?
1
andrewhxism 2019-07-08 10:41:16 +08:00
语法上的区别,等效于没区别
|
2
maxco292 2019-07-08 10:43:28 +08:00 via Android
void main 是 c 式语法,不推荐,其他没啥区别
|
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[]更符合直觉。
|
4
shijingshijing OP @maxco292
恩,这个我知道,我忘记在哪本书里看到过了,好像说的是其实 C 还是 UNIX,自始至终都没有强制要求 int 返回值,也没有要求一定要有 argc 和 argv 参数。(具体记不太清楚了)我不是纠结 int main(void)和 void main(void)这两种。 @andrewhxism 我的意思是,为什么阿里要求强制用 String[] args 而否定 String args[],如果刚好项目里面用到了一个第三方提供的源代码库,里面都是 String args[],他们会怎么处理。。。 这个要求只是简单的为了避免引入书写错误而规定的?还是有其他性能或者质量方面的考量? |
5
haozhang 2019-07-08 11:05:08 +08:00 via Android
char *argv[]更好,明确表明是 char *数组。
|
6
ejq 2019-07-08 11:05:58 +08:00 via Android 1
@shijingshijing String[] 是 Java 风格
|
7
gunavy 2019-07-08 11:46:15 +08:00
辛亏你不做 js,要不你的整个人生都是纠结的😂
|
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 |
9
ipwx 2019-07-08 12:03:52 +08:00 1
老哥,C++ 没有 string[] argv 这种用法。
不同语言的代码风格,能参考嘛? |
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 设计成什么样传进去的参数就是什么样了。 |
11
GeruzoniAnsasu 2019-07-08 12:23:32 +08:00 via Android 1
这个现象是这样的
几乎所有的 ide 自动生成 main 函数用的都是 char *argv[]这种形式 但不翻一下文档你一般很难想起来*和[]哪个优先级高,要不要写括号。所以手写开头一般 char**,不会错,也不用考虑括号 破案:你你看到的那些底层库估计是 vim 写的,没有 ide 自动生成 main |
12
vkhsyj 2019-07-08 12:26:16 +08:00
两个是一样的,两个都是通俗写法,个人觉得 char* argv[] 语义更加明确
|
13
codehz 2019-07-08 12:53:05 +08:00
@Mithril #10 linux 也是这样(入口点是 crt 的函数,准备好各种参数后然后再调用 main (不然你试试强行改入口点到 main 会不会崩(
|
14
Mithril 2019-07-08 13:33:46 +08:00
@codehz 我的意思是 Windows 的 PE 入口是个 int (*)(void),linux 不太清楚是不是。但是 CRT 这些东西都是一致的。
|
15
shijingshijing OP @ipwx
我说 Visual Studio 明显是说 C#啊,你在 Visual Studio 里面新建 C++工程,VS 自动给你生成的 boilerplate 是这种: ``` int main(array<System::String ^> ^args) ``` 我现在其实最想知道的是为什么阿里 Java 开发手册里规定强制使用 String[] args 且否定了 String args[],比较好奇原因。 |
16
wdv2ly 2019-07-08 17:30:39 +08:00 via Android
好奇,java 有第二种写法吗?反正 c#没有第二种写法
|
17
jamesliu96 2019-07-08 17:35:51 +08:00 via Android
想开点😶
|
18
smdbh 2019-07-08 18:01:58 +08:00
面向 memory 编程,就不纠结了
|
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# 中不存在的写法(不符合句法)。 |
20
ipwx 2019-07-08 19:00:56 +08:00
@shijingshijing 首先,Java 不存在指针。C# safe 代码不存在指针。所以你的出发点就不一样。
其次,<System::String^> 不是 C++ 项目,是 .NET C++ 项目,两者截然不同。 |
21
shijingshijing OP |
22
msg7086 2019-07-08 22:12:38 +08:00 via Android 2
这贴几乎可以当作提问的智慧的教科书般的反面教材了。
标题问 C 的指针和数组,内容说 Java,顺便提到一个无关的 C#,最后再回过头否定自己标题的提问,顺便把回答者批判一番。 还行。我佛了。 |
23
shijingshijing OP |
24
msg7086 2019-07-08 23:39:37 +08:00
@shijingshijing 这么教科书式的帖子 Block 了多可惜。
|
25
zhao4dick25cm 2019-07-09 07:34:18 +08:00 via iPhone
没必要,少写一点是一点,就是 int main()
|
26
jaskle 2019-07-09 07:47:22 +08:00 via Android
数组式好理解,c 这种东西,写法千万种
|
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];} 看起来就比较乱 |
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**,则用意相对是清楚的。 |
29
FrankHB 2019-07-09 12:05:12 +08:00
@geelaw 我不记得有哪个版本的 C/C++ 标准提出“正确的 main 的签名只有以下 2 种”(姑且搁置“签名”是指什么的问题)。请核实并指出来源。
|
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 相等。 |
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 仍支持 () 参数列表这样明确不指定参数类型的情况。所以虽然你说的“日常理解的含义”虽然不算有很大的普遍问题,但这个上下文中反而会引起混乱。 |
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()。 |
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)仍然更好。 |