我正在尝试更深入地研究以下行:
char* filename="file.txt";
当我们使用时我会这样做fopen()
;
我的问题是:
filename
应该保存一个字符的地址(core2Duo 中的 36 位)。为什么我们要在里面放一个“字符串”?为什么编译器不会生成错误,因为您可能存储了一个永远不存在的地址?
我正在尝试更深入地研究以下行:
char* filename="file.txt";
当我们使用时我会这样做fopen()
;
我的问题是:
filename
应该保存一个字符的地址(core2Duo 中的 36 位)。为什么我们要在里面放一个“字符串”?
为什么编译器不会生成错误,因为您可能存储了一个永远不存在的地址?
文件名应该包含一个字符的地址(core2Duo 中的 36 位),为什么我们要在其中放入一个“字符串”?
该表达式char* filename="file.txt";
是一个有效的表达式。原因是 C 中的字符串文字类型char[N]
很容易衰减为char*
,并且char[N]
兼容char*
。char*
因此,您可以像在此表达式中所做的那样将字符串地址分配给指针变量。
为什么编译器不会生成错误,因为您可能存储了一个永远不存在的地址?
请阅读以下三点:
不,在表达式char* filename="file.txt";
中,filename
分配了一个有效地址。它在概念上类似于(假设地址以 21 开头):
filename 21 22 23 24 25 26 27 28 29
+---+ +------------------------------------+
|21 |---> |'f'|'i'|'l'|'e'|'.'|'t'|'x'|'t'|'\0'|
+---+ +------------------------------------+
filename pointing to string, a valid address.
并且它没有给出任何错误或警告:试试下面的代码示例:
示例 1:
#include<stdio.h>
int main(int argc, char **argv){
char* filename = "filename.txt";
printf("%s", filename);
return 0;
}
编译它:
:~$ gcc y.c -Wall -pedantic
~$ ./a.out
filename.txt
没有错误和警告,它的编译和执行完美。
关于您对我的回答的评论:
中的字符串C
是位复杂的数据结构,然后是简单的值变量,如int
, char
, float
.
对于基本数据类型:
int i = 5; You are assigning value 5, to variable i char c = 'A'; You are assigning value 'A' to char variable c. float f = 5.5f; You are assigning value 5.5f to float variable f.
但是对于字符串,当你这样做时:
char* filename = "filename.txt";
然后你实际上将字符串的地址分配"filename.txt"
给 char* 指针变量filename
。
而如果你这样做:
char filename[] = "filename.txt";
// ^ notice [] in declaration
然后你分配字符串“filename.txt”;到一个 char 数组filename[]
,这里的filename
类型是char[]
.
要了解两个声明中的更多尊重,请阅读:sizeof(&arr) 返回什么?
字符串文字可能会在您使用它的上下文中为您提供值或地址依赖性。例如尝试:printf(" address: %p, value: %s", "Hello", "Hello");
编译器不负责验证地址是否合法。编译器移植代码并检查语法错误(例如不可编译的类型不匹配)。假设如果您将假地址分配给指针,它不会给您警告(如果您正确键入强制转换地址)。考虑下面的例子:
示例 2:
#include<stdio.h>
int main(int argc, char **argv){
char* filename = "filename.txt";
char* ptr = (char*)0x020202;
printf("%s %s\n", filename, ptr);
return 0;
}
编译:
$ gcc y.c -Wall -pedantic
它不会产生任何错误或警告。因为在语法上一切都很好并且有效。
(而ptr
分配了一个可能不存在的假地址)。
好吧,编译ptr
分配了假地址的 example-2 代码很好,即使使用棒检查标志选项,编译器也不会生成任何错误/警告:-Wall -pedantic
.
但是执行这段代码是错误的。它尝试访问ptr
在 printf 语句中分配的内存地址,并且程序将表现异常(在不同的执行实例中)。在C
-Language 标准中,它被称为: 未定义的行为。
当您执行此代码时,操作系统(但不是编译器)检测到进程的内存权限违规 - 对有效内存的无效访问会给出:SIGSEGV 并且对无效地址的访问会给出:SIGBUS。这可能会导致进程终止/崩溃,并出现一些分段错误和coredump。
要了解并了解访问非法内存时会发生什么,请阅读:strcat() 实现有效,但最后会导致核心转储。
文件名应该包含一个字符的地址(core2Duo 中的 36 位),为什么我们要在其中放入一个“字符串”?
可悲的事实是我们不是。我们在其中放置了一个指向字符串文字的第一个字符的指针。字符串"file.txt"
的类型char[9]
是 ,当分配给指针时,它会衰减为char *
。但是您应该将其分配给指向 的指针const char
,因为修改它是非法的(导致未定义的行为)。读这个。
为什么编译器不会生成错误,因为您可能存储了一个永远不存在的地址?
打扰一下,但是你在说什么不存在的地址?字符串文字中第一个字符的地址始终明确有效!
(但即使是——编译器对语义知之甚少,如果有的话。它根本不可能总是警告你是否有可能使用无效指针。当然,有时它可以,但不要有很高的期望.)
请阅读一些不错的 C 编程书籍,它们解释了什么是指针和数组。
字符串文字 like"file.txt"
是一个char[]
数组(但您应该将其视为const char[]
因为在字符串文字内部分配 like"abc"[1]='D';
是未定义的行为,味道不好,并且gcc -Wall
会警告您)。数组可以(并且通常)被理解为指向它们的第一个单元格(索引为 0)的指针 - 换句话说,数组被衰减为指针 - 因此您可以将字符串文字分配给char
指针。
但是,数组不是 C 中的指针。例如,sizeof
某个数组是每个元素大小的合适倍数,特别是sizeof("abcde") == 6
(因为每个字符串文字中的终止空字节)。但是sizeof
some 指针与指向区域的大小无关,并且在大多数系统上,所有指针都具有相同的大小,即机器字的大小。
您需要明确地向编译器询问所有警告(例如gcc -Wall
在 Linux 上)以获取它们。
fopen
在程序实际运行之前不会评估对的调用。