在C语言中提到程序名称的最佳实践

8

在提及程序名称时,什么被认为是最佳实践?我见过以下几种方式:

#define PROGRAM_NAME "myprog"
printf("this is %s\n", PROGRAM_NAME);

以及:

printf("this is %s\n", argv[0]);

我知道,第二种方法在程序没有从 $PATH 调用时会给我 ./myprog 而不是 myprog,而第一种方法将保证程序名称的一致性。
但是还有其他方面使一种方法优于另一种方法吗?

如果你想要一个常量,就使用一个常量(例如在全局层面,const char *const PROGRAM_NAME = "myprog";)。这样做可以确保该字符串只在内存中存储一次。 - mk12
8个回答

5

我试图兼顾两个方面的优点:

char const * program_name;

int main(int argc, char **argv) {
   program_name = argv[0];
   //...
}

如果您需要在其他文件中使用program_name,可以像这样声明它:

如果您需要在其他文件中使用program_name,可以像这样声明它:

extern char const * program_name;

我声明“char const *”是因为我希望它成为指向常量数据的指针。我不确定这部分是否正确。


不错。通过回答“两者都是”,避开了哪个更好的问题,但还不错。 - MJB

5
第二种方法在你有多个链接时更优。在*nix系统中,有时行为取决于你如何调用程序。因此,硬编码程序名称显然会是一个问题--它永远无法检查到。

这句话的意思是“程序的行为取决于你如何调用它”,我真的无法理解它。 - guest
@guest:例如,gccg++可能是相同的可执行文件,它会检查调用它的名称并相应地修改链接器选项。我记不清它是否真的是这样了。 - Steve Jessop
默认情况下,grep会打印匹配的行。此外,还可以使用三个变体程序egrep、fgrep和rgrep。egrep与grep -E相同。fgrep与grep -F相同。rgrep与grep -r相同。 - György Andrasek
啊,以前从未听说过这个概念,不过很有道理。 - guest
一些程序会检查 argv[0] 来确定它们应该如何行动。例如,BusyBox 实现了一组 Unix 实用工具,通常安装在 /bin/BusyBox 上,并有许多符号链接指向它:例如,/bin/ls 将指向 /bin/BusyBox,并且当您运行 'ls' 时,它将运行 BusyBox,然后确定需要像 ls 一样行动。 - Ori Pessach
2
另一个例子是 viview,它是 vi 的只读版本,但是它们使用完全相同的代码和可执行文件。将其称为 view 只是以只读方式打开文件。 - MJB

2
我通常使用argv[0],或者如果可能的话使用basename(argv[0])。从用户的角度来看,如果他们重命名或硬链接可执行文件(或其他人为他们这样做),那么他们希望从中获得的消息出现在他们使用的名称下,而不是编译时使用的其他名称,他们可能知道也可能不知道。
同样地,如果您在将来发现想要以不同的名称和选项编译程序以提供不同版本,您是否想要在#define周围包装一个#ifndef,并确保它通过编译器命令行定义:-DPROGRAM_NAME=myprog_demo,还是只是想做到这一点并且它可以工作?
例外情况可能是,如果您的使用说明是从man页或其他文档中提取的,则可能确实希望将程序名称硬编码到其中。但是,您可能也不会使用#define
实现不需要提供argv[0],因此为了最佳的可移植性实践,也要处理该情况。然而,如果您的系统没有提供它,那么用户实际上也不会在任何类型的终端上看到消息。
顺便说一句:
#define PROGRAM_NAME "myprog"
puts("this is " PROGRAM_NAME);

1
basename 不是可移植的。它只被 POSIX(在 libgen.h 中)所需。此外,请记住它可能会修改您提供的字符串,并且返回的指针可能是原始指针的偏移量,或者是静态分配的字符串(因此不要尝试释放它给您的内容,并且不要立即尝试释放您提供的内容)。除非您100%确定了涉及的内存管理并且不关心可移植性,否则我建议避免使用它。 - mk12
1
因此,“如果可能的话”。但你说得对,basename(argv[0])本身就是一个有点可疑的表达式,它假设你不打算再次使用这些参数。 - Steve Jessop

1

第二种方法也可以给你像/usr/bin/myprog这样的字符串,如果你以这种方式执行它;basename应该给出可执行文件的名称(你可以认为是你的程序的名称)...除非它是符号链接...(在这种情况下,你有链接的名称...可以用来在程序行为中做出选择)。

第一种方法“修复”了程序名称,无论用户如何重命名可执行文件或符号链接(甚至硬链接)。


是的,我使用过一些产品,看起来好像有几个可执行文件,但实际上只有一个文件,并且有多个链接指向它。这可能看起来很疯狂,但它是一种通过多个版本来保持多个工具同步的好方法。 - Jim Tshr

1

这并不完全回答你关于编程最佳实践的问题,但我认为你也应该考虑到对用户最有益的因素。

我个人喜欢程序使用 argv[0] 自称,即调用命令,而不是一些由程序员硬编码的随机名称。以下是一些例子,硬编码名称时会很烦人或至少没有帮助:

  • 我已经创建了一个指向程序的链接
  • 我已经出于某种原因重命名了二进制文件
  • 我在我的 $PATH 中的不同目录中有多个相同基本名称的可执行文件
  • 程序给我提示其他的调用方式,例如在 "usage" 信息中

我唯一想要看到硬编码程序名称的情况是当我使用 GUI 应用程序时。 我不希望将 "~/foo/bar.pl" 作为窗口标题。


0

如果你手头没有argv,那么前者比后者更优秀。

#define PROGRAM_NAME "myprog"

void salute()
{
     // no argv available 

     printf("Hello from %s\n", PROGRAM_NAME );
}

void main( int argc, char** argv ) 
{
     salute();
}

这会带来很大的差异吗?我不认为 argcargv 的成本太高。 - guest
啊,我现在明白你的意思了。是的,非常有道理。 - guest
1
我理解你的意思,但是在你的例子中,你基本上创建了一个全局变量。如果你需要 program_name,那么将它(argv)作为参数传递给 salute(),你的问题就解决了。 - MJB
是的,对于一些方法来说是有效的,如果名称需要在多个地方使用,并且大多数函数已经有了一个很长的参数列表,在全局常量可用时再使用一个参数是荒谬的。无论哪种情况,我认为jonhcatfish是更好的选择。 - OscarRyz
@支持:如果名称需要在多个地方使用,可能您应该传递一个日志记录器对象(如果绝对必要,可以将其设置为全局),而不仅仅是程序名称。 - Steve Jessop
我明白了。虽然我从未做过这件事,但我确实理解。我讨厌非常长的参数列表,因为我总是会在某个地方打错字并发送错误的顺序。 - MJB

0

这取决于argv是否在作用域内...


0

全局变量在正确使用时是可以的。与#define相比,它们可能更好,因为至少在调试时可以轻松检查它们。

然而,basename让我有些疑虑。

static inline char *my_basename(char const *name) // neither GNU nor POSIX...
{
    char *b = strrchr(name, '/');
    if (b)
        return b + 1;
    return (char*)name;
}

/* ... */

char *program_name;

/* ... */

void salute()
{
     // no argv available 

     printf("Hello from %s\n", program_name);
}

int main(int argc, char* argv[]) 
{
     program_name = my_basename(argv[0]);
     salute();
     return 0;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接