Linux C/C++学习4:关于字符串常量的几个基础问题

本文通过实例详细探讨了C/C++中字符串常量的性质和操作,包括指针形式和数组形式的字符串常量,以及动态内存分配的字符串。分析了它们在内存中的存储位置、可修改性及潜在的错误和限制,如尝试修改字符串常量导致的Segmentation fault。
摘要由CSDN通过智能技术生成


1. 相同字符串常量的地址是否相同?

  • 源码
// File: strdemo1.c
#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# gcc -Wall -g strdemo1.c -o strdemo1
root@ubuntu:/opt/constval# ./strdemo1 
ap=0x55995a62d004, bp=0x55995a62d004, a==b
cp=0x55995a62f010, dp=0x7fffdebdfa8c, c!=d
  • 结论
    (1) 指针形式的字符串常量(*a 和 *b):地址相同,指向同一段存储空间,即仅存储一份相同的字符串。
    (2) 数组形式的字符串常量(c[] 和 d[]):地址不同,指向不同的存储空间,即存储了两份相同的字符串。

2. 指针形式的字符串常量(char *)可以更改吗?

  • 源码
// File: strdemo2.c
#include <stdio.h>
char *a = "hello world";	// 已初始化
char *b;					// 未初始化
int main(int argc, char *argv[])
{
	*a = "get out";   		// compile [-Wint-conversion] and execute Segmentation fault
	// *(a+3) = '-';     	// compile normal and execute Segmentation fault
	// *a = 123;         	// compile normal and execute Segmentation fault
	// *a = 't';         	// compile normal and execute Segmentation fault
	
	// *b = "get out";   	// compile [-Wint-conversion] and execute Segmentation fault
	// *(b+3) = '-';     	// compile normal and execute Segmentation fault
	// *b = 123;         	// compile normal and execute Segmentation fault
	// *b = 't';         	// compile normal and execute Segmentation fault

	// printf("++ap=%p, ", ++a);	// normal
	// printf("++ac=%s\n", a);		// normal
	// printf("++bp=%p, ", ++b);	// normal (++bp=0x1, because *b is uninitialized)
	// printf("++bc=%s\n", b);		// compile normal and execute Segmentation fault
	
	return 0;
}
  • 执行
    执行结果参见源码中的注释,读者可以自行验证每一行。例如"*a = “get out”; //compile [-Wint-conversion] and execute Segmentation fault",即编译时报 [-Wint-conversion] 警告,执行时报Segmentation fault,如下所示。
root@ubuntu:/opt/constval# gcc -Wall -g strdemo2.c -o strdemo2
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# ./strdemo2
Segmentation fault (core dumped)
  • 结论
    (1) 无论 char * 是否初始化,在编译时,输入int型、char型无警告,输入字符串型报[-Wint-conversion]警告;在执行时,均会报Segmentation fault错误。
    (2) 指针形式的字符串常量存放在不可以更改的静态内存存储区域,更改其指向的任何数据都是不允许的。
    (3) 指针本身是一个变量,单独预留一个存储位置,用于存储字符串常量的地址(指向首字符地址),可进行++a等类似的操作。

3. 数组形式的字符串常量(char [])可以更改吗?

  • 源码
// File: strdemo3.c
#include <stdio.h>
char c[12] = "hello world";
int main(int argc, char *argv[])
{
    printf("cp=%p, cv=%s\n", c, c);

	c[5] = '-';							// normal
    printf("cp=%p, cv=%s\n", c, c);
    *(c+5) = '&';						// normal
    printf("cp=%p, cv=%s\n", c, c);
    
    *c = "get out";						// compile [-Wint-conversion] and execute normal
    printf("\"get out\" pointer is %p\n", "get out");
    printf("cp=%p, cv=%s\n", c, c);

	// *(++c) = '#';					// compile error: lvalue required as increment operand
	
	return 0;
}
  • 执行
root@ubuntu:/opt/constval# gcc -Wall -g strdemo3.c -o strdemo3
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# ./strdemo3
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)可以更改吗?

  • 源码
// File: strdemo4.c
#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# gcc -Wall -g strdemo4.c -o strdemo4
root@ubuntu:/opt/constval# ./strdemo4
bp=0x559b61db72a0, bc=hello world
bp=0x559b61db72a0, bc=hello-world
  • 结论
    (1) 用动态申请的方法给一个字符串赋值,那么该字符串在程序运行过程中的值依然可以修改。

5. 参考文献

  1. C语言字符串定义
  2. gcc_warning:assignment makes integer from pointer without a cast
  3. ASCII码字符对照表
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值