linux c 获取绝对路径各种方法分析

本文探讨了在Linux环境下,如何准确获取程序的绝对路径,对比了getcwd、argv[0]和realpath等方法的局限性,并最终介绍了通过/proc/self/exe获取程序绝对路径的可靠方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

昨天肚腩群里有人问LINUX下C如何获取程序绝对路径。初看这问题,肚腩觉得很简单啊,就用getcwd或者argv[0]就可以了。写了个程序试试,

#include <unistd.h>

int main(int argc, char *argv[])
{
    char buffer[100];

    getcwd(buffer, sizeof(buffer));
    printf("The current directory is: %s\n", buffer);
    printf("prog name : %s \n", argv[0]);

    return 0;
}

编译:

gcc -Wall -pipe -g -static -o getpath getpath.c 

发现,getcwd不行,这个函数返回的是当时的执行路径,如果在其他目录执行该程序,如~/.这样调用就不行了。
argv[0]也不行,程序的第一个参数未必是绝对路径,比如使用/home/../../root/getpath这样调用,那么第一个参数就变成了这样的相对路径。

肚腩改进了这个程序,用realpath把相对路径转换成绝对路径:

#include <unistd.h>
main(int argc, char * argv[])
{
      char  resolved_path[80];

      realpath(argv[0], resolved_path);
      printf("resolved_path: %s\n", resolved_path);
      sleep(300);
}

OK.这个时候argv[0]用相对路径调用的问题解决了。

BUGS

但随后肚腩又发现存在另外一个问题,比如把程序放到/usr/bin这些存在于环境变量PATH的目录下,然后在/home目录下通过程序名直接执行这个程序,输出的程序绝对路径就变成了/home/getpath,实际上应该是/usr/bin/getpath。

 

ELF文件里是否有相关信息

还有没有其它办法,肚腩先试了下在这个ELF文件下是否有一些绝对路径的信息。

执行:

strings getpath| grep getpath

自然是没有找到了。从另一个方向想想,如果绝对路径的信息保存在ELF文件中,那么改变一个程序的路径,连程序的内容都要修改?所以是不可能滴。

 

尝试从进程内存获取

那么这个绝对路径的信息,只能是程序运行的时候,加入到进程空间里面的。我们看看是否能否进程内存获取?
写一个代码看下系统的内存地址分布情况:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int a = 0; //全局初始化区
char *p1; //全局未初始化区

main(int argc, char **argv)
{
    int b; //栈
    char s[] = "abc"; //栈
    char *p2; //栈
    char *p3 = "123456";// 123456\0在常量区,p3在栈上。
    static int c = 0;// 全局(静态)初始化区
    static int uc, uc1, uc2;// 全局(静态)未初始化区

    p1 = (char *)malloc(10);
    p2 = (char *)malloc(20);

    //分配得来得10和20字节的区域就在堆区。
    strcpy(p1, "123456");// 123456\0放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。

    printf("堆p1 \t\t\t%p\n", p1);
    printf("堆p2 \t\t\t%p\n", p2);
    printf("栈&p3 \t\t\t%p\n", &p3);
    printf("栈&p2 \t\t\t%p\n", &p2);
    printf("栈s \t\t\t%p\n", s);
    printf("栈&s[1] \t\t%p\n", &s[1]);
    printf("栈&b \t\t\t%p\n", &b);
    printf("main地址\t\t%p\n", main);
    printf("文本常量区\t\t%p\n", p3);
    printf("全局初始化区\t\t%p\n", &a);
    printf("(静态)初始化区\t%p\n", &c);
    printf("全局未初始化区\t\t%p\n", &p1);
    printf("(静态)未初始化区\t%p\n", &uc);
    printf("(静态)未初始化区\t%p\n", &uc1);
    printf("(静态)未初始化区\t%p\n", &uc2);

    char *p;
    if (p = getenv("PATH"))
    {
                printf("USER=%p\n", p);
    }

    printf("ARGC\t%p\n", &argc);
    printf("ARGV\t%p\n", argv);
    printf("ARGV\t%p\n", *argv);
    sleep(300);
}

这里要指出一下,因为肚腩的系统是x86-64.64为系统,这里用gcc打印地址的是要注意是使用%p,不用去用%x.用uname -a可以查看系统版本。

编译一下:

gcc -Wall -pipe -g -static -o memoryshow memoryshow.c 

执行,结果是:

[root@localhost ~]# ./memoryshow 
堆p1                    0xa3e8b0
堆p2                    0xa3e8d0
栈&p3                   0x7fff94fec4a0
栈&p2                   0x7fff94fec4a8
栈s                     0x7fff94fec4b0
栈&s[1]                 0x7fff94fec4b1
栈&b                    0x7fff94fec4b4
main地址                0×400494
文本常量区              0x47f3b0
全局初始化区            0x6a6e50
(静态)初始化区        0x6a6e54
全局未初始化区          0x6a9ba8
(静态)未初始化区      0x6a6e58
(静态)未初始化区      0x6a6e5c
(静态)未初始化区      0x6a6e60
USER=0x7fff94fece29
ARGC    0x7fff94fec49c
ARGV    0x7fff94fec598
ARGV    0x7fff94fec82b

查看下这个进程的map情况:

[root@localhost ~]# ps -ef | grep showmemory
root      2715  2150  0 21:58 pts/1    00:00:00 grep showmemory
[root@localhost ~]# ps -ef | grep memory
root      2713  2087  0 21:58 pts/0    00:00:00 ./memoryshow
root      2717  2150  0 21:58 pts/1    00:00:00 grep memory
[root@localhost ~]# cat /proc/2713/maps 
00400000-004a6000 r-xp 00000000 fd:00 524196                             /root/memoryshow
006a6000-006a7000 rw-p 000a6000 fd:00 524196                             /root/memoryshow
006a7000-006aa000 rw-p 00000000 00:00 0 
00a3d000-00a60000 rw-p 00000000 00:00 0                                  [heap]
7f050a5c2000-7f050a5c3000 rw-p 00000000 00:00 0 
7fff94fd8000-7fff94fed000 rw-p 00000000 00:00 0                          [stack]
7fff94fff000-7fff95000000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]

再次重复一下操作,肚腩需要看下进程哪些地址会改变,那些不会改变。网上一般的32为线性分布,和肚腩的系统对不上号。所以自己写个程序一看就清晰明了了。

[root@localhost ~]# ./memoryshow 
堆p1                    0x76b8b0
堆p2                    0x76b8d0
栈&p3                   0x7fff9987a9f0
栈&p2                   0x7fff9987a9f8
栈s                     0x7fff9987aa00
栈&s[1]                 0x7fff9987aa01
栈&b                    0x7fff9987aa04
main地址                0×400494
文本常量区              0x47f3b0
全局初始化区            0x6a6e50
(静态)初始化区        0x6a6e54
全局未初始化区          0x6a9ba8
(静态)未初始化区      0x6a6e58
(静态)未初始化区      0x6a6e5c
(静态)未初始化区      0x6a6e60
USER=0x7fff9987ce29
ARGC    0x7fff9987a9ec
ARGV    0x7fff9987aae8
ARGV    0x7fff9987c82b

其对应的MAP情况:

[root@localhost ~]# cat /proc/2713/maps 
cat: /proc/2713/maps: No such file or directory
[root@localhost ~]# ps -ef | grep memory
root      2719  2087  0 21:59 pts/0    00:00:00 ./memoryshow
root      2722  2150  0 21:59 pts/1    00:00:00 grep memory
[root@localhost ~]# cat /proc/2719/maps 
00400000-004a6000 r-xp 00000000 fd:00 524196                             /root/memoryshow
006a6000-006a7000 rw-p 000a6000 fd:00 524196                             /root/memoryshow
006a7000-006aa000 rw-p 00000000 00:00 0 
0076a000-0078d000 rw-p 00000000 00:00 0                                  [heap]
7f9987494000-7f9987495000 rw-p 00000000 00:00 0 
7fff99868000-7fff9987d000 rw-p 00000000 00:00 0                          [stack]
7fff999ff000-7fff99a00000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]

这里可以看到,前面三个段的地址是不变的,但堆开始,后面的高位地址段每次运行都会改变。

如果包含文件绝对路径的数据是落在前面三个段,那么可以通过地址直接获取,如果是在后面,可能就要通过计算获取。

用GDB调试下,看看何种情况:

[root@localhost ~]# gdb memoryshow
Reading symbols from /root/memoryshow…done.
(gdb) b main
Breakpoint 1 at 0x4004a3: file memoryshow.c, line 11.
(gdb) r
Starting program: /root/memoryshow 
Breakpoint 1, main (argc=1, argv=0x7fffffffe5c8) at memoryshow.c:11
11      char s[] = "abc"; //栈
(gdb) b 44
Breakpoint 2 at 0x4006db: file memoryshow.c, line 44.
(gdb) c
Continuing.
堆p1                    0x6ab8b0
堆p2                    0x6ab8d0
栈&p3                   0x7fffffffe4d0
栈&p2                   0x7fffffffe4d8
栈s                     0x7fffffffe4e0
栈&s[1]                 0x7fffffffe4e1
栈&b                    0x7fffffffe4e4
main地址                0×400494
文本常量区              0x47f3b0
全局初始化区            0x6a6e50
(静态)初始化区        0x6a6e54
全局未初始化区          0x6a9ba8
(静态)未初始化区      0x6a6e58
(静态)未初始化区      0x6a6e5c
(静态)未初始化区      0x6a6e60
USER=0x7fffffffedff
ARGC    0x7fffffffe4cc
ARGV    0x7fffffffe5c8
ARGV    0x7fffffffe81b

Breakpoint 2, main (argc=1, argv=0x7fffffffe5c8) at memoryshow.c:45
45      sleep(300);
(gdb) x/30s 0x7fffffffedff
0x7fffffffedff:  "/usr/local/maven/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/jrockit/bin:/usr/local/redis/bin:/usr/local/mysql/bin:/root/bin"
0x7fffffffee9c:  "MAIL=/var/spool/mail/root"
0x7fffffffeeb6:  "nodeName=_0_218"
0x7fffffffeec6:  "_=/usr/bin/gdb"
0x7fffffffeed5:  "PWD=/root"
0x7fffffffeedf:  "JAVA_HOME=/usr/local/jrockit"
0x7fffffffeefc:  "LANG=en_US.UTF-8"
0x7fffffffef0d:  "configDir=/data/config"
0x7fffffffef24:  "LINES=43"
0x7fffffffef2d:  "HISTCONTROL=ignoredups"
0x7fffffffef44:  "HOME=/root"
0x7fffffffef4f:  "SHLVL=1"
0x7fffffffef57:  "M2_HOME=/usr/local/maven"
0x7fffffffef70:  "LOGNAME=root"
0x7fffffffef7d:  "SSH_CONNECTION=192.168.0.158 62555 192.168.0.98 22"
0x7fffffffefb0:  "LESSOPEN=|/usr/bin/lesspipe.sh %s"
0x7fffffffefd2:  "G_BROKEN_FILENAMES=1"
0x7fffffffefe7:  "/root/memoryshow"   <–注意这里
0x7fffffffeff8:  ""
0x7fffffffeff9:  ""
0x7fffffffeffa:  ""
0x7fffffffeffb:  ""
0x7fffffffeffc:  ""
0x7fffffffeffd:  ""
0x7fffffffeffe:  ""
0x7fffffffefff:  ""
0x7ffffffff000:  <Address 0x7ffffffff000 out of bounds>
0x7ffffffff000:  <Address 0x7ffffffff000 out of bounds>
0x7ffffffff000:  <Address 0x7ffffffff000 out of bounds>
0x7ffffffff000:  <Address 0x7ffffffff000 out of bounds>

这里可以看到程序运行的绝对路径在envstring中保存,而envstring却在栈中,而栈地址是每次运行都变化的。而且如果继续测试,还可以发现,调用的方式不同,这个绝对路径对应栈开始地址的位置也是不同。而且机器不同,算法也不同。所以直接从进程内存里面获取绝对路径也不太靠谱。

 

从/proc/self/exe获取

肚腩在网上查了下,最常用的方法是利用进程映射文件,用readlink读取/proc/self/exe这个符号链接指向的文件.插一段,如何判断链接是符号链接或者是硬链接,主要是看文件的INODE,一样就是硬链接,不一样就是符号链接。用ls -li查看。

网上找到的一段代码:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define MAXBUFSIZE 1024
int main ( int argc, char * argv[] )
{
    char buf[ MAXBUFSIZE ];

    int count;

    count = readlink( "/proc/self/exe", buf, MAXBUFSIZE );

    if ((count < 0) || (count >= MAXBUFSIZE))
    {
        printf( "Failed\n" );

        return (EXIT_FAILURE);
    }

    buf[ count ] = '\0';

    printf( "/proc/self/exe -> [%s]\n", buf );

    return (EXIT_SUCCESS);
} /* end of main */

 

exe文件权限问题

可能有人会担心/proc/self/exe这个文件的权限问题,肚腩觉得不需要担心,该目录下的文件权限是由系统确认的,用户是不能修改的。因此读的是自己进程在PROC目录下的映射目录,用户权限肯定是有的。如果去读其它用户运行的程序在/proc下的映射目录,就不一定有权限了。

 

<think>好的,用户问的是如何在C语言获取文件绝对路径。这个问题需要我仔细思考一下,因为C语言标准库本身可能没有直接提供这个功能,但不同的操作系统有不同的方法。我得先回忆一下各个平台的相关函数。 首先,用户可能是在Windows或者Linux/macOS下开发。对于POSIX系统(比如Linux和macOS),我记得有realpath函数,这个函数可以将相对路径转换为绝对路径。需要包含stdlib.h头文件,然后调用realpath,传入相对路径和一个缓冲区,或者让函数自己分配内存。但需要注意缓冲区的大小,或者处理可能的错误,比如路径不存在的情况。 然后是Windows系统的情况。Windows API中有_fullpath函数,它类似于realpath,但可能属于微软的扩展。使用这个函数也需要包含正确的头文件,比如stdlib.h,不过可能还需要定义某些宏或者在编译时链接特定的库。另外,Windows的路径结构可能涉及盘符,所以在处理时要注意这一点。 接下来要考虑的是跨平台的解决方案。用户可能希望他们的代码在多个系统上都能运行,这时候需要用预处理器指令判断当前的操作系统,然后分别调用相应的函数。例如,使用#ifdef _WIN32来区分Windows和其他系统,分别使用_fullpath和realpath。 另外,用户可能关心错误处理。比如,如果路径不存在或者没有权限,这些函数会返回NULL,并设置errno。这时候需要检查返回值,并处理错误情况,比如输出错误信息或者采取其他措施。 还有动态分配内存的问题。例如,realpath函数如果第二个参数为NULL,会自己分配内存,这时候用户需要记得释放内存,避免内存泄漏。而Windows的_fullpath可能也需要预先分配足够大的缓冲区,或者使用动态分配的方式。 可能用户在实际应用中需要获取当前可执行文件的路径,这时候可能需要其他方法,比如argv[0],但这可能不总是绝对路径,特别是在某些环境下运行时。不过这个问题可能超出了当前问题的范围,用户可能只是需要基本的路径转换。 最后,需要给出示例代码,展示如何在两种系统下正确使用这些函数,并说明注意事项,比如头文件、错误处理、内存管理等。这样用户可以直接参考示例,应用到自己的项目中。</think>在C语言获取文件绝对路径需要依赖操作系统提供的API,不同平台实现方式不同。以下是分平台解决方案和注意事项: 一、POSIX系统(Linux/macOS) 使用`realpath()`函数: ```c #include <stdlib.h> #include <stdio.h> int main() { char *abs_path = realpath("test.txt", NULL); // 第二个参数为NULL时自动分配内存 if (abs_path == NULL) { perror("获取绝对路径失败"); return 1; } printf("绝对路径:%s\n", abs_path); free(abs_path); // 必须释放内存 return 0; } ``` 二、Windows系统 使用`_fullpath()`函数: ```c #include <stdlib.h> #include <stdio.h> #include <windows.h> int main() { char buffer[_MAX_PATH]; if (_fullpath(buffer, "test.txt", _MAX_PATH) == NULL) { perror("获取绝对路径失败"); return 1; } printf("绝对路径:%s\n", buffer); return 0; } ``` 三、跨平台解决方案 ```c #include <stdio.h> #include <stdlib.h> #ifdef _WIN32 #include <windows.h> #define PATH_MAX _MAX_PATH #else #include <limits.h> #endif void get_abs_path(const char *rel_path, char *abs_path) { #ifdef _WIN32 _fullpath(abs_path, rel_path, PATH_MAX); #else realpath(rel_path, abs_path); #endif } int main() { char path[PATH_MAX]; get_abs_path("test.txt", path); printf("绝对路径:%s\n", path); return 0; } ``` 注意事项: 1. 错误处理:必须检查返回指针是否为NULL 2. 缓冲区大小:Windows建议使用`_MAX_PATH`(260字节),Linux建议`PATH_MAX`(4096字节) 3. 内存管理:POSIX自动分配版本需要手动释放 4. 文件存在性:路径对应的文件/目录必须存在才能正确解析 5. 符号链接:在Linux系统会解析符号链接,Windows会保留链接形式 扩展应用场景: - 获取当前可执行文件路径:可通过`argv[0]`结合路径解析实现 - 处理相对路径:`../`等相对路径会被自动解析 - 网络路径:Windows支持`\\server\share`格式的网络路径 建议在实际开发中封装成统一的路径处理模块,结合错误处理机制,确保程序的健壮性和跨平台兼容性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值