为什么我可以在这个例子中修改 char *?

Why am I able to modify char * in this example?

我无法理解 char* 的工作原理。

在下面的示例中,struses 由 main() 调用。 我创建了一个 buf 来存储 const 变量,因为我想制作 s1 的可修改副本,然后我只调用 sortString().

这个版本对我来说很有意义,因为我知道 char[] 可以修改:

#include "common.h"
#include <stdbool.h>
void sortString(char string[50]);

bool struses(const char *s1, const char *s2) 
{

    char buf[50];
    strcpy(buf, s1);  // <===== input = "perpetuity";
    sortString(buf);
    printf("%s\n", buf); // prints "eeipprttuy"
    return true;
}

void sortString(char string[50]) 
{
    char temp;
    int n = strlen(string);
    for (int i = 0; i < n - 1; i++)
    {
        for (int j = i + 1; j < n; j++)
        {
            if (string[i] > string[j])
            {
                temp = string[i];
                string[i] = string[j];
                string[j] = temp;
            }
        }
    }
}

但是,在这个版本中,我故意将类型更改为char*,这应该是只读的。为什么我仍然得到相同的结果?

#include "common.h"
#include <stdbool.h>
void sortString(char *string);

bool struses(const char *s1, const char *s2)
{

    char buf[50];
    strcpy(buf, s1); 
    sortString(buf);
    printf("%s\n", buf);
    return true;
}

void sortString(char *string)  // <==== changed the type
{
    char temp;
    int n = strlen(string);
    for (int i = 0; i < n - 1; i++)
    {
        for (int j = i + 1; j < n; j++)
        {
            if (string[i] > string[j])
            {
                temp = string[i];
                string[i] = string[j];
                string[j] = temp;
            }
        }
    }
}

这就是为什么我认为 char * 是只读的。尝试修改 read[0]:

后出现总线错误
char * read = "Hello";
read[0]='B';// <=== Bus error
printf("%s\n", read); 

编译器调整该函数声明的数组类型参数的类型

void sortString(char string[50]);

指向元素类型的指针

void sortString(char *string);

所以例如这些函数声明是等价的并且声明相同的一个函数

void sortString(char string[100]);
void sortString(char string[50]);
void sortString(char string[]);
void sortString(char *string);

在此函数内

void sortString(char *string)

使用字符数组 buf 存储传递数组的副本(或通过指向它的指针传递的字符串文字)

char buf[50];
strcpy(buf, s1);
sortString(buf);

所以没有问题。 s1 可以是指向字符串文字的指针。但是字符串文字的内容被复制到正在更改的字符数组buf

至于这段代码

char * read = "Hello";
read[0]='B';
printf("%s\n", read); <=== still prints "Hello"

那么它有未定义的行为,因为您不能更改字符串文字。

来自 C 标准(6.4.5 字符串文字)

7 It is unspecified whether these arrays are distinct provided their elements have the appropriate values. If the program attempts to modify such an array, the behavior is undefined.

注意在C++中与C相反的字符串文字有常量字符数组类型。在 C 中也建议使用限定符 const 声明指向字符串文字的指针,以避免未定义的行为,例如

const char * read = "Hello";

顺便说一下,函数 sortString 对传递的字符串中的元素进行了冗余交换。最好用下面的方式来声明和定义

// Selection sort
char * sortString( char *s ) 
{
    for ( size_t i = 0, n = strlen( s ); i != n; i++ )
    {
        size_t min_i = i;

        for ( size_t j = i + 1; j != n; j++ )
        {
            if ( s[j] < s[min_i] )
            {
                min_i = j;
            }
        }

        if ( i != min_i )
        {
            char c = s[i];
            s[i] = s[min_i];
            s[min_i] = c;
        }
    }

    return s;
}

char *不代表只读。 char * 仅表示指向 char.

的指针

您可能被告知不能修改字符串文字,例如 "Hello"。事实并非如此。一个正确的说法是 C 标准没有定义当您尝试修改字符串文字时会发生什么,并且 C 实现通常将字符串文字放在只读内存中。

我们可以使用 const 限定符定义对象,表示我们不打算修改它们并允许编译器将它们放置在只读内存中(尽管它不是必须的)。如果我们从头开始定义 C 语言,我们将指定字符串文字是 const 限定的,来自字符串文字的指针将是 const char *.

但是,C刚开始开发的时候,并没有const,字符串字面量产生的指针只是char *const 限定符后来出现,更改字符串文字为 const 限定为时已晚,因为所有旧代码都使用 char *.

因此,char * 可能指向不应修改的字符串文字中的字符(因为未定义行为)。但是char *一般来说并不代表只读。

关于

char * read = "Hello";
read[0]='B';
printf("%s\n", read);   // still prints "Hello"

您被 C 规范中的向后兼容性问题绊倒了。

字符串常量 是只读的。然而,char * 是指向 可修改 数据的指针。 字符串常量的类型 应该是 const char [N] 其中 N 是由常量的内容给出的 char 的数量加一。但是,const 在原始 C 语言(C89 之前)中并不存在。因此,在 1989 年,出现了大量使用 char * 指向字符串常量的代码。因此,C 委员会将字符串常量的类型设为 char [N],即使它们是只读的,以保持代码正常工作。

通过指向字符串常量的 char * 写入会触发未定义的行为;任何事情都可能发生。我本以为会发生崩溃,但写入被丢弃也不足为奇。

在 C++ 中,字符串常量的类型实际上是 const char [N],上面的片段将无法编译。一些 C 编译器有一个可选模式,您可以打开该模式将字符串常量的类型更改为 const char [N];例如,GCC 和 clang 有 -Wwrite-strings 命令行选项。将此模式用于新程序是个好主意。

您关于 char* 指向的区域不可修改的前提是错误的。这是完美的线:

char s[] = "abc";       // Same as: char s[4] = { 'a', 'b', 'c', 0 };
char *p = s;            // Same as: char *p = &(s[0]);
*p = 'A';
printf("%s\n", p);      // Abc

Demo

您出错的原因是您试图修改由字符串文字创建的字符串。这是未定义的行为:

char *p = "abc";
*p = 'A';               // Undefined behaviour
printf("%s\n", p);

人们通常会为此类字符串使用 const char *

const char *p = "abc";
*p = 'A';               // Compilation error.
printf("%s\n", p);

Demo

你的长例子可以简化为你的最后一个问题。

This is why I think char * is read only, get bus error after attempt to modify read[0]

char * read = "Hello";
read[0]='B';
printf("%s\n", read); <=== Bus error

"Hello" 是一个 字符串文字 。尝试修改由总线错误显示的字符串文字。

您的指针正在引用不应修改的内存。

如何解决?您需要定义引用可修改对象的指针

char * read = (char []){"Hello"};
read[0]='B';
printf("%s\n", read); 

因此,如您所见,将其声明为可修改并不是使其可修改。