C语言文件读取问题
在使用C语言读取相对路径下的文件时,可能会遇到“无法找到文件的问题”。
测试环境:
系统:
gcc:
Apple clang version 14.0.0 (clang-1400.0.29.202)
Target: x86_64-apple-darwin22.2.0
Thread model: posix
InstalledDir: /Library/Developer/CommandLineTools/usr/bin
目录结构:
.
|____.DS_Store
|____a.c
|____a.txt
测试:
#include <stdio.h>
int main(){
char text[20];
if(freopen("./a.txt","r",stdin) == NULL){
perror("无法打开文件");
}
else{
if(fgets(text,sizeof(text),stdin) == NULL){
printf("文件读取失败\n");
return 1;
}
fclose(stdin);
printf("%s\n",text);
}
return 0;
}
在两种不同的运行方式下,代码运行的结果不同:
在终端且在可执行文件的目录下编译并运行文件:
但如果直接双击或在主目录(又或是除可执行文件所在的其它目录)下运行已经编译的文件:
这是双击文件时终端所显示的
原因探究
我将通过一段代码,输出程序所操作的目录(或者有其它更容易理解的称呼)。
#include <stdio.h>
#include <unistd.h>
#include <limits.h>
int main(){
char cwd[PATH_MAX];
if (getcwd(cwd,sizeof(cwd)) != NULL) {
printf("当前目录: %s\n", cwd);
} else {
perror("getcwd()失败");
}
return 0;
}
再次按照先前的方式运行文件:

由此可见,代码所操作的目录,是终端所在的目录(或称之为终端所操作的目录),代码所读取文件的情况会因此而改变。
解决方法
可以直接获取文件的绝对路径:
这是适用于macOS的代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h> /*提供PATH_MAX。*/
#include <mach-o/dyld.h> /*提供_NSGetExecutablePath*/
#include <libgen.h> /*提供dirname (optional)*/
#include <unistd.h>
char *concatenate(const char *str1,const char *str2){ /*字符拼接*/
size_t len1 = strlen(str1);
size_t len2 = strlen(str2);
char *result = (char*)malloc(len1 + len2 + 1);
if(result == NULL){
return NULL;
}
strcpy(result,str1);
strcat(result,str2);
return result;
}
char *the_path(){ /*macOS获取绝对路径的核心代码*/
char path[PATH_MAX];
uint32_t size = sizeof(path);
char *real_path = NULL;
char *dir_path = NULL;
if(_NSGetExecutablePath(path,&size) == 0){ /*macOS特供函数*/
printf("获取路径为:%s\n",path);
real_path = realpath(path,NULL); /*规范路径格式*/
if(real_path != NULL){
printf("绝对路径为:%s\n",real_path);
dir_path = strdup(real_path);
if(dir_path != NULL){ /*只获取路径*/
char *last_slash = strrchr(dir_path, '/');
if(last_slash != NULL){
*last_slash = '\0';
}
printf("目录路径为:%s\n",dir_path);
}
}
else{
perror("realpath");
}
}
else{
fprintf(stderr,"缓冲太小,需要 %u bytes\n",size);
}
if(dir_path != NULL){
free(real_path);
return dir_path;
}
return NULL;
}
int main(){
char cwd[PATH_MAX];
if (getcwd(cwd,sizeof(cwd)) != NULL) {
printf("当前目录: %s\n", cwd);
} else {
perror("getcwd()失败");
}
char text[20];
if(freopen(concatenate(the_path(),"/a.txt"),"r",stdin) == NULL){
perror("无法打开文件");
}
else{
if(fgets(text,sizeof(text),stdin) == NULL){
printf("文件读取失败\n");
return 1;
}
fclose(stdin);
printf("%s\n",text);
}
return 0;
}
再次通过不同方式运行:

文件读取成功,但是这只支持macOS,所以接下来我尝试在linux下实现。
Linux获取绝对路径
我将在Termius上远程控制我的树莓派,这是我的测试环境:

a.c目前还没有被编写,现在我将再次测试linux是否支持直接使用相对路径:

由此可见,linux与先前的问题一样,我们仍然需要获取绝对路径。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h> /*提供readlink*/
#include <limits.h> /*提供PATH_MAX*/
char *the_path(){
char path[PATH_MAX];
char *dir_path = NULL;
ssize_t len = readlink("/proc/self/exe",path,sizeof(path) - 1);
if(len != -1){
path[len] = '\0';
printf("绝对路径:%s\n",path);
dir_path = strdup(path);
char *last_slash = strrchr(dir_path,'/');
if(last_slash != NULL){
*last_slash = '\0';
}
printf("目录路径为:%s\n",dir_path);
return dir_path;
}
else{
perror("readlink");
}
return NULL;
}
char *concatenate(const char *str1,const char *str2){
size_t len1 = strlen(str1);
size_t len2 = strlen(str2);
char *result = (char*)malloc(len1 + len2 + 1);
if(result == NULL){
return NULL;
}
strcpy(result,str1);
strcat(result,str2);
return result;
}
int main(){
char text[20];
if(freopen(concatenate(the_path(),"/a.txt"),"r",stdin) == NULL){
perror("无法打开文件\n");
}
else{
if(fgets(text,sizeof(text),stdin) == NULL){
printf("文件读取失败\n");
return 1;
}
fclose(stdin);
printf("%s\n",text);
}
return 0;
}
运行结果如下:
windows获取绝对路径
windows的问题与先前一致,测试就跳过了。
环境:
#include <stdio.h>
#include <windows.h>
#include <tchar.h> /*提供 _TCHAR 宏和 _tprintf 等函数*/
#include <limits.h>
#include <string.h>
char *the_path() {
_TCHAR path[MAX_PATH];
DWORD length = GetModuleFileName(NULL,path,MAX_PATH);
char *dir_path = NULL;
if (length > 0) {
_tprintf(_T("绝对路径(Unicode): %s\n"),path);
dir_path = strdup(path);
if(dir_path != NULL){
char *last_slash = strrchr(dir_path,'\\');
if(last_slash != NULL){
*last_slash = '\0';
}
printf("目录路径为:%s\n",dir_path);
return dir_path;
}
}
else {
DWORD error = GetLastError();
_ftprintf(stderr,_T("失败代码: %lu\n"),error);
}
return NULL;
}
char *concatenate(const char *str1,const char *str2){ /*字符拼接*/
size_t len1 = strlen(str1);
size_t len2 = strlen(str2);
char *result = (char*)malloc(len1 + len2 + 1);
if(result == NULL){
return NULL;
}
strcpy(result,str1);
strcat(result,str2);
return result;
}
int main() {
char text[20];
if(freopen(concatenate(the_path(),"/a.txt"),"r",stdin) == NULL){
perror("无法打开文件");
}
else{
if(fgets(text,sizeof(text),stdin) == NULL){
printf("文件读取失败\n");
return 1;
}
fclose(stdin);
printf("%s\n",text);
}
return 0;
}
为了使PowerShell正常显示中文,代码与测试文本都以GBD格式存储。
效果如下:
跨平台支持
为了让代码可以在不同系统上顺利运行,同时适配一些没有考虑的系统,我将代码中和到了一起,并添加了argv获取方式作为保底。
最后的代码是这样的:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#if defined(_WIN32) || defined(_WIN64)
#include <windows.h>
#include <tchar.h>
#elif defined(__linux__)
#include <unistd.h>
#include <limits.h>
#elif defined(__APPLE__)
#include <mach-o/dyld.h>
#include <libgen.h>
#include <unistd.h>
#endif
char *concatenate(const char *str1,const char *str2) {
size_t len1 = strlen(str1);
size_t len2 = strlen(str2);
char *result = (char*)malloc(len1 + len2 + 1);
if(result == NULL){
return NULL;
}
strcpy(result,str1);
strcat(result,str2);
return result;
}
/*核心代码开头*/
char *get_path_from_argv(const char *argv0){
if(argv0 == NULL){
return NULL;
}
char *real_path = realpath(argv0,NULL);
if(real_path != NULL){
printf("使用argv[0]获取绝对路径:%s\n",real_path);
char *dir_path = strdup(real_path);
if(dir_path != NULL){
char *last_slash = strrchr(dir_path,'/');
if(last_slash == NULL){
last_slash = strrchr(dir_path,'\\');
}
if(last_slash != NULL){
*last_slash = '\0';
}
printf("目录路径为:%s\n",dir_path);
}
free(real_path);
return dir_path;
}
return NULL;
}
char *the_path(int argc,char *argv[]){
#if defined(_WIN32) || defined(_WIN64)
_TCHAR path[MAX_PATH];
DWORD length = GetModuleFileName(NULL,path,MAX_PATH);
char *dir_path = NULL;
if(length > 0){
_tprintf(_T("绝对路径(Unicode): %s\n"), path);
dir_path = strdup(path);
if(dir_path != NULL){
char *last_slash = strrchr(dir_path,'\\');
if(last_slash != NULL){
*last_slash = '\0';
}
printf("目录路径为:%s\n",dir_path);
return dir_path;
}
}
else{
DWORD error = GetLastError();
_ftprintf(stderr, _T("失败代码: %lu\n"),error);
}
char *argv_path = get_path_from_argv(argc > 0 ? argv[0] : NULL);
if(argv_path != NULL){
return argv_path;
}
return NULL;
#elif defined(__linux__)
char path[PATH_MAX];
char *dir_path = NULL;
ssize_t len = readlink("/proc/self/exe",path,sizeof(path) - 1);
if(len != -1){
path[len] = '\0';
printf("绝对路径:%s\n",path);
dir_path = strdup(path);
char *last_slash = strrchr(dir_path,'/');
if(last_slash != NULL){
*last_slash = '\0';
}
printf("目录路径为:%s\n",dir_path);
return dir_path;
}
else{
perror("readlink");
}
char *argv_path = get_path_from_argv(argc > 0 ? argv[0] : NULL);
if(argv_path != NULL){
return argv_path;
}
return NULL;
#elif defined(__APPLE__)
char path[PATH_MAX];
uint32_t size = sizeof(path);
char *real_path = NULL;
char *dir_path = NULL;
if(_NSGetExecutablePath(path,&size) == 0){
printf("获取路径为:%s\n",path);
real_path = realpath(path,NULL);
if(real_path != NULL){
printf("绝对路径为:%s\n",real_path);
dir_path = strdup(real_path);
if(dir_path != NULL){
char *last_slash = strrchr(dir_path,'/');
if(last_slash != NULL){
*last_slash = '\0';
}
printf("目录路径为:%s\n",dir_path);
}
}
else{
perror("realpath");
}
}
else{
fprintf(stderr,"缓冲太小,需要 %u bytes\n",size);
}
if(dir_path != NULL){
free(real_path);
return dir_path;
}
char *argv_path = get_path_from_argv(argc > 0 ? argv[0] : NULL);
if(argv_path != NULL){
return argv_path;
}
return NULL;
#else
fprintf(stderr, "不支持的操作系统\n");
char *argv_path = get_path_from_argv(argc > 0 ? argv[0] : NULL);
if(argv_path != NULL){
return argv_path;
}
return NULL;
#endif
}
/*核心代码结尾*/
int main(int argc,char *argv[]){ /*如果使用argv,需要加上“int argc,char *argv[]”这一段*/
#if defined(__APPLE__)
char cwd[PATH_MAX];
if(getcwd(cwd,sizeof(cwd)) != NULL){
printf("当前目录: %s\n", cwd);
}
else{
perror("getcwd()失败");
}
#endif
char text[20];
char *full_path = concatenate(the_path(argc,argv),"/a.txt"); /*所拼接的路径不能为“./a.txt”,需要为“/a.txt”的格式*/
if(freopen(full_path,"r",stdin) == NULL){
perror("无法打开文件");
}
else{
if(fgets(text,sizeof(text),stdin) == NULL){
printf("文件读取失败\n");
free(full_path);
return 1;
}
fclose(stdin);
printf("%s\n",text);
}
free(full_path);
return 0;
}














Top comments (0)