【问题标题】:Finding the function caller in C在 C 中查找函数调用者
【发布时间】:2009-10-19 00:51:40
【问题描述】:

大家好,我只是想知道是否可以获取在函数中运行的程序的名称?

这是一个例子:

假设我调用了:./runProgram

main() {

A();

}

function A() {

// Possible to retrieve "runProgram" if I cannot use main's argc(argv) constants??
}

【问题讨论】:

  • 你能澄清你的问题吗?您声明您“不能使用 main 的 argc(argv) 常量”,但不清楚是否:(1)您没有意识到您可以轻松地将参数 argc/argv 从 main() 存储或传递到其他地方;或者 (2) 是否有一些技术限制阻止您使用 argc/argv。
  • 如果有技术原因,这是一个更有趣的问题;-p 例如,该函数可能位于现有程序的插件中,因此无法更改签名或 main()。 ..
  • 哦,没注意到他用的是零参数主版本oO
  • 10 个回答非常愚蠢的问题。我们真的需要更好的问题在这里=/
  • @toto: 来吧......这样的问题是我精神错乱的游乐场。

标签: c function


【解决方案1】:

依赖编译器,所以:

$ cc --version
i686-apple-darwin10-gcc-4.2.1 (GCC) 4.2.1 (Apple Inc. build 5646)

制作程序

$ more x.c
int main(int argc, char *argv[]) {
      printf("program: %s\n", argv[0]);
    foo();
}


int foo() {    
}

$ make x
cc     x.c   -o x
x.c: In function ‘main’:
x.c:2: warning: incompatible implicit declaration of built-in function ‘printf’
$ ./x 
program: ./x

获取 argc/v 变量的全局名称

$ nm ./x
0000000100000efe s  stub helpers
0000000100001048 D _NXArgc
0000000100001050 D _NXArgv
0000000100001060 D ___progname
0000000100000000 A __mh_execute_header
0000000100001058 D _environ
                 U _exit
0000000100000eeb T _foo
0000000100000eb8 T _main
                 U _printf
0000000100001020 s _pvars
                 U dyld_stub_binder
0000000100000e7c T start

添加全局名称,声明为 extern,并考虑修改。

$ more x2.c
int main(int argc, char *argv[]) {
      printf("program: %s\n", argv[0]);
    foo();
}


int foo() {
    extern char **NXArgv;
    printf("in foo: %s\n", NXArgv[0]);

}

逃跑

$ make x2
cc     x2.c   -o x2
x2.c: In function ‘main’:
x2.c:2: warning: incompatible implicit declaration of built-in function ‘printf’
x2.c: In function ‘foo’:
x2.c:9: warning: incompatible implicit declaration of built-in function ‘printf’
$ ./x2 
program: ./x2
in foo: ./x2

请不要告诉我妈妈。

【讨论】:

  • 尽管有这些其他弊端,你至少可以#include <stdio.h> 让编译器闭嘴。它不会受伤。
  • @Chris:我很着急。是的,我本可以添加标题,但考虑到答案的趋势,添加 2>&1 >/dev/null 会更好......
  • 哈哈哈。首先,我实际上对“请不要告诉我妈妈”和“你是个野人”都很感兴趣。非常感谢您的帮助。
【解决方案2】:

这在“标准 C”中是不可能的。如果您喜欢 jiggery-pokery,您也许可以查看程序的环境变量以找到命令行。以下适用于 FreeBSD:

/*    _ _                                               _                   
     (_|_) __ _  __ _  ___ _ __ _   _       _ __   ___ | | _____ _ __ _   _ 
     | | |/ _` |/ _` |/ _ \ '__| | | |_____| '_ \ / _ \| |/ / _ \ '__| | | |
     | | | (_| | (_| |  __/ |  | |_| |_____| |_) | (_) |   <  __/ |  | |_| |
    _/ |_|\__, |\__, |\___|_|   \__, |     | .__/ \___/|_|\_\___|_|   \__, |
   |__/   |___/ |___/           |___/      |_|                        |___/  */

#include <stdio.h>

extern char ** environ;

void A ()
{
    char ** p;
    for (p = environ; *p; p++)
        printf ("%s\n", * p);
}

int main ()
{
    A ();
}

但是,在 C 本身中,与 JavaScript 和 Perl 等语言不同,没有办法查看堆栈并找出是谁给你打电话的。

【讨论】:

  • 我认为我非常小心地限定了“在 C 本身中”,而不是在特定编译器中。
【解决方案3】:

GetCurrentProcessId(); 将为您获取当前的进程 ID。从那里,您需要将其与当前运行的进程名称相匹配。

有关第 2 步的更多信息,请参阅 this code project article

【讨论】:

  • 以 NULL 作为第一个参数调用 GetModuleFileName 会更直接
【解决方案4】:

通常你会为此使用一个全局变量。

const char *g_argv0;
void A()
{
    printf("program is %s\n", g_argv0);
}
int main(int argc, char *argv[])
{
    g_argv0 = argv[0];
    A();
    return 0;
}

通过对这个想法的细微变化,如果需要,您可以将整个命令行数组保存在全局变量中。

【讨论】:

    【解决方案5】:

    程序的名称将存储在 argv[0] 中。

    请注意,这不一定与首先想到的文件名相同。例如,如果有指向程序的符号链接并且使用该名称调用程序,那么这就是将存储在 argv[0] 中的内容。

    因此,例如,您可以使用以下程序:

    #include <stdio.h>
    
    int main(int argc, char **argv)
    {
        printf("%s\n", argv[0]);
        return 0;
    }
    

    这将产生以下行为:

    $ cc t.c
    $ ./a.out 
    ./a.out
    $ ln -s a.out foo
    $ ./foo
    ./foo
    

    请注意,shell 替换发生在名称到达程序之前:

    $ alias bar=./foo
    $ bar
    ./foo
    

    【讨论】:

    • OP 特别要求提供一种不使用 argv 的方法
    【解决方案6】:
    int main(int argc, char *argv[]) {
      printf("program: %s\n", argv[0]);
    
      //pass argv[0] to the desired function.
    }
    

    【讨论】:

    • 我不认为这是他要问的。
    • 这肯定是比他要求的更好的实现目标的方法。
    • 他特别说他无权访问args
    • @Chris:有趣……你怎么会落到这样的境地?
    【解决方案7】:

    对于 Windows,您可以使用 GetCommandLine 函数。也许有类似的适用于 Linux 的 API。

    【讨论】:

      【解决方案8】:

      您可能可以从 getpid 中找到导致您的进程执行的完整命令,但具体如何完成则因平台而异。

      【讨论】:

        【解决方案9】:

        忽略您上面的代码是糟糕的 C 并且实际上更接近伪代码的事实......

        没有。您可以将argv[0] 分配给一个全局变量,以便其他地方的代码可以访问它,也可以将它作为参数传递。我怀疑是否有一种标准的 C 方法可以进入另一个函数的局部变量。这听起来像是一个相当糟糕的主意(以我的拙见)。

        使用全局:

        char *progname;
        
        void A(void)
        {
            // do stuff with progname
        }
        
        int main(int argc, char **argv)
        {
            progname = *argv;
            A();
            return 0;
        }
        

        作为参数传递:

        void A(char *progname)
        {
            // do stuff with progname
        }
        
        int main(int argc, char **argv)
        {
            A(*argv);
            return 0;
        }
        

        编辑:我们大多数人都错过了它,因为它被隐藏在 cmets 中相当具有欺骗性,但你说你不能使用 argv。我只想指出,任何不使用argv 的解决方案都将是不可移植的,最好的答案是继续使用argv,因为它是按照标准提供给您的,您没有任何可以想象的理由不能使用argv。这就像说“如何在不使用stdin 文件句柄的情况下将文本打印到控制台?”你可以做到,但你为什么要这样做?

        【讨论】:

          【解决方案10】:

          在Linux下,你可以查看/proc/self/cmdline(或者用getpid()找到进程ID,然后查看/proc/[pid]/cmdline;前者是后者的快捷方式。

          【讨论】:

            【解决方案11】:

            x86 上的 C 调用约定具有这样的堆栈框架布局。

            ... *0x4000000c = 0x60000000(第二个参数) *0x40000008 = 0x00000001(第一个参数) *0x40000004 = 0x20000000(返回地址) *(旧 %esp = %ebp = 0x40000000)= 0x3ffffff0(旧 %ebp) *0x3ffffffc = 0x00000000(第一个本地) *0x3ffffff8 = 0x00000000(第二个本地) ... *(%esp = 0x3ffffff0)(帧结束)

            因此,要获取调用者的参数,请从(%ebp) 开始并向上走。 GCC 的 __builtin_frame_address 扩展有助于第一步。

            A() {
                void **ebp = __builtin_frame_address(1);
                void **ra = ebp + 1;
                int *argc = ra + 1;
                char ***argv = argc + 1;
                int i;
                for (i = 0; i < *argc; i++) printf("[%d] %s\n", i, (*argv)[i]);
            }
            a(int argc, char **argv) { A(); }
            main(int argc, char **argv) { a(argc, argv); }
            

            很遗憾,main 有点特殊,所以直接从main 调用A 可能会崩溃。

            此外,为了节省工作和释放寄存器,优化编译器可能会省略此标准堆栈帧设置,这也会导致此操作失败。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2010-11-11
              • 1970-01-01
              • 2010-10-05
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2021-01-17
              相关资源
              最近更新 更多