Linux C/C++学习4:关于字符串常量的几个基础问题
1. 相同字符串常量的地址是否相同?
# include <stdio.h>
char * a = "hello world" ;
char c[ ] = "hello world" ;
int main ( int argc, char * argv[ ] )
{
char * b = "hello world" ;
printf ( "ap=%p, bp=%p, a%sb\n" , a, b, ( ( a== b) ? "==" : "!=" ) ) ;
char d[ ] = "hello world" ;
printf ( "cp=%p, dp=%p, c%sd\n" , c, d, ( ( c== d) ? "==" : "!=" ) ) ;
return 0 ;
}
root@ubuntu:/opt/constval
root@ubuntu:/opt/constval
ap = 0x55995a62d004, bp = 0x55995a62d004, a == b
cp = 0x55995a62f010, dp = 0x7fffdebdfa8c, c!= d
结论 (1) 指针形式的字符串常量(*a 和 *b):地址相同,指向同一段存储空间,即仅存储一份相同的字符串。 (2) 数组形式的字符串常量(c[] 和 d[]):地址不同,指向不同的存储空间,即存储了两份相同的字符串。
2. 指针形式的字符串常量(char *)可以更改吗?
# include <stdio.h>
char * a = "hello world" ;
char * b;
int main ( int argc, char * argv[ ] )
{
* a = "get out" ;
return 0 ;
}
执行 执行结果参见源码中的注释,读者可以自行验证每一行。例如"*a = “get out”; //compile [-Wint-conversion] and execute Segmentation fault",即编译时报 [-Wint-conversion] 警告,执行时报Segmentation fault,如下所示。
root@ubuntu:/opt/constval
strdemo2.c: In function ‘main’:
strdemo2.c:6:5: warning: assignment to ‘char’ from ‘char *’ makes integer from pointer without a cast [ -Wint-conversion]
6 | *a = "get out" ; // compile [ -Wint-conversion] and execute Segmentation fault
| ^
root@ubuntu:/opt/constval
Segmentation fault ( core dumped)
结论 (1) 无论 char * 是否初始化,在编译时,输入int型、char型无警告,输入字符串型报[-Wint-conversion]警告;在执行时,均会报Segmentation fault错误。 (2) 指针形式的字符串常量存放在不可以更改的静态内存存储区域,更改其指向的任何数据都是不允许的。 (3) 指针本身是一个变量,单独预留一个存储位置,用于存储字符串常量的地址(指向首字符地址),可进行++a等类似的操作。
3. 数组形式的字符串常量(char [])可以更改吗?
# include <stdio.h>
char c[ 12 ] = "hello world" ;
int main ( int argc, char * argv[ ] )
{
printf ( "cp=%p, cv=%s\n" , c, c) ;
c[ 5 ] = '-' ;
printf ( "cp=%p, cv=%s\n" , c, c) ;
* ( c+ 5 ) = '&' ;
printf ( "cp=%p, cv=%s\n" , c, c) ;
* c = "get out" ;
printf ( "\"get out\" pointer is %p\n" , "get out" ) ;
printf ( "cp=%p, cv=%s\n" , c, c) ;
return 0 ;
}
root@ubuntu:/opt/constval
strdemo3.c: In function ‘main’:
strdemo3.c:16:8: warning: assignment to ‘char’ from ‘char *’ makes integer from pointer without a cast [ -Wint-conversion]
16 | *c = "get out" ;
| ^
root@ubuntu:/opt/constval
cp = 0x557291653010, cv = hello world
cp = 0x557291653010, cv = hello-world
cp = 0x557291653010, cv = hello*world
"get out" pointer is 0x557291651012
cp = 0x557291653010, cv = ello& world
结论 (1) 用字符串常量初始化的数组(char [])可以更改,因为它不是存放在不可更改的内存区域。 (2) 对于 *c = “get out” 进一步说明:*c 实际上指向的是c[12]的第一个字符,将 “get out” 赋值给 *c时,强制将其地址0x557291651012截取后两位,即char型0x12,赋值给c[12]的第一个字符。因为0x12在ASCII码表中对应的是控制字符,即非打印字符,所以其输出为ello&world。 (3) 编译器会把数组名c看作是数组首元素的地址&c[0]的同义词,即c是个地址常量。可以用c+1来标识下一个元素,但不能使用++c;c[12]在计算机内存中被分配一个有12个元素的数组(其中每个元素对应一个字符,还有一个附加的元素对应结束的空字符’\0’),每个元素都被初始化为相应的字符;通常,被引用的字符串存储在可执行文件的数据段部分,当程序被加载到内存中时,字符串也被加载到内存中,把被引用的字符串复制到数组中。
4. 动态申请内存的字符串(malloc)可以更改吗?
# include <stdio.h>
# include <stdlib.h>
int main ( int argc, char * argv[ ] )
{
char * a = "hello world" ;
char * b = NULL ;
if ( ( b = ( char * ) malloc ( sizeof ( char ) * 12 ) ) == NULL )
{
perror ( "malloc fail!" ) ;
return - 1 ;
}
while ( * a != '\0' ) * b++ = * a++ ;
* b = '\0' ;
b -= 11 ;
printf ( "bp=%p, bc=%s\n" , b, b) ;
* ( b+ 5 ) = '-' ;
printf ( "bp=%p, bc=%s\n" , b, b) ;
free ( b) ;
return 0 ;
}
root@ubuntu:/opt/constval
root@ubuntu:/opt/constval
bp = 0x559b61db72a0, bc = hello world
bp = 0x559b61db72a0, bc = hello-world
结论 (1) 用动态申请的方法给一个字符串赋值,那么该字符串在程序运行过程中的值依然可以修改。
5. 参考文献
《C语言字符串定义 》 《gcc_warning:assignment makes integer from pointer without a cast 》 《ASCII码字符对照表 》