C语言 如何正确地分配一个新的字符串值?

vc6uscn9  于 2023-06-21  发布在  其他
关注(0)|答案(6)|浏览(152)

我试图理解如何用C语言以最干净/最安全的方式解决这个微不足道的问题。下面是我的例子:

#include <stdio.h>

int main(int argc, char *argv[])
{
    typedef struct
    {
        char name[20];
        char surname[20];
        int unsigned age;
    } person;

    // Here I can pass strings as values...how does it work?
    person p = {"John", "Doe", 30};

    printf("Name: %s; Age: %d\n", p.name, p.age);

    // This works as expected...
    p.age = 25;

    //...but the same approach doesn't work with a string
    p.name = "Jane";

    printf("Name: %s; Age: %d\n", p.name, p.age);

    return 1;
}

编译器的错误是:
main.c:在函数'main'中:main.c:18:错误:从类型“char *”赋值给类型“char[20]”时不兼容的类型
我知道C(不是C++)没有String类型,而是使用char s的数组,所以另一种方法是修改示例结构以保存char s的指针:

#include <stdio.h>

int main(int argc, char *argv[])
{
    typedef struct
    {
        char *name;
        char *surname;
        int unsigned age;
    } person;

    person p = {"John", "Doe", 30};

    printf("Name: %s; Age: %d\n", p.name, p.age);

    p.age = 25;

    p.name = "Jane";

    printf("Name: %s; Age: %d\n", p.name, p.age);

    return 1;
}

这和预期的一样有效,但我想知道是否有更好的方法来做到这一点。

gopyfrb3

gopyfrb31#

第一个例子不起作用,因为你不能给数组赋值--在这方面,数组的工作方式(有点)像常量指针。你可以做的是复制一个新值到数组中:

strcpy(p.name, "Jane");

如果你事先知道字符串的最大大小,可以使用Char数组,例如。在第一个例子中,您100%确定名称将适合19个字符(而不是20个,因为总是需要一个字符来存储终止零值)。
相反,如果你不知道字符串的最大可能大小,或者你想优化内存使用,指针会更好。避免为名称“John”保留512个字符。但是,对于指针,您需要动态分配它们所指向的缓冲区,并在不再需要时释放它,以避免内存泄漏。

**更新:**动态分配缓冲区示例(使用您第二个示例中的struct定义):

char* firstName = "Johnnie";
char* surname = "B. Goode";
person p;

p.name = malloc(strlen(firstName) + 1);
p.surname = malloc(strlen(surname) + 1);

p.age = 25;
strcpy(p.name, firstName);
strcpy(p.surname, surname);

printf("Name: %s; Age: %d\n",p.name,p.age);

free(p.surname);
free(p.name);
qc6wkl3g

qc6wkl3g2#

把字符串看作抽象对象,把char数组看作容器。字符串可以是任何大小,但容器必须至少比字符串长度大1(以容纳空终止符)。
C对字符串的语法支持很少。没有字符串运算符(只有字符数组和字符指针运算符)。您不能指定字符串。
但是你可以调用函数来帮助实现你想要的。
这里可以使用strncpy()函数。为了最大限度的安全,我建议遵循以下模式:

strncpy(p.name, "Jane", 19);
p.name[19] = '\0'; //add null terminator just in case

另外,还可以看看strncat()memcpy()函数。

hwamh0ep

hwamh0ep3#

这两个结构体是不同的。初始化第一个结构体时,将分配大约40个字节的内存。当你初始化第二个结构时,大约分配了10个字节的内存。(实际金额取决于体系结构)
可以使用字符串文字(字符串常量)初始化字符数组。这就是为什么
person p = {“John”,“Doe”,30};
在第一个例子中工作。
在C语言中不能赋值(传统意义上的)字符串。
当代码执行时,您拥有的字符串字面量(“John”)将加载到内存中。当你用这些文字初始化一个数组时,字符串被复制到一个新的内存位置。在第二个示例中,您只是将指针复制到字符串字面量(的位置)。做一些类似的事情:

char* string = "Hello";
*string = 'C'

可能会导致编译或运行时错误(我不确定)。这是一个坏主意,因为你正在修改文字字符串“Hello”,例如在微控制器上,它可能位于只读存储器中。

ifsvaxew

ifsvaxew4#

第一个结构体是字符数组[],第二个结构体是指向字符串的指针 *(对于64位机器,大小为8字节)。根据Stephen Kochan的书“Programming in C”,C允许您分配常量字符串的唯一时间是在定义和初始化char数组时,如

char name[20] = { "John Doe" };

甚至没有

char name[20];
name = { "John Doe" };

在char *name的情况下name是字符指针,而不是数组。当你做了

p.name = "Jane";

它指向另一个字符串对象。

person p = { .surname = "Doe", .name = "Johnny", .age = 30 };
printf("Ptr. value:\tp.name: 0x%p;\tp.surname: 0x%p\n", p.name, p.surname);
p.name = "Spy, watch out!";
printf("Ptr. value:\tp.name: 0x%p;\tp.surname: 0x%p\n", p.name, p.surname);

输出:

Ptr. value:     p.name: 0x00007FF726F7B16C;     p.surname: 0x00007FF726F7B174
Ptr. value:     p.name: 0x00007FF726F7ACE8;     p.surname: 0x00007FF726F7B174

但是,在字符数组[]的情况下,在执行

strcpy(p.name, "Jane");

为了改变其内容,缓冲区p.name[]的地址永远不会改变。
C和Python之间一个有趣的相似之处是Python的String是不可变的,类似于C的字符串指针,其中字符串文字是只读的。Python的List是可变的,类似于C的字符数组。

>>> name = "John"
>>> print(hex(id(name)))
0x261654235f0
>>> name = "Jane"
>>> print(hex(id(name)))
0x261654237b0
>>> type(name)
<class 'str'>
>>> name[1] = 'o'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment
>>> name = list(name)
>>> type(name)
<class 'list'>
>>> name
['J', 'a', 'n', 'e']
>>> name[1] = 'o'
>>> name
['J', 'o', 'n', 'e']
>>> name = ''.join(name)
>>> name
'Jone'
>>> type(name)
<class 'str'>
>>>
9nvpjoqh

9nvpjoqh5#

在这两种情况下,你都在写:

p.age = 25;
p.name = "Jane";
  • 在第一种情况下,p.name是一个数组,在C语言中无法对数组赋值
  • 在第二种情况下,p.name是一个char*,并且可以将其分配给字符串文字,因为字符串文字是char的数组(数组可以转换为指针)

您可以使用strcpymemcpy等函数。如其他响应中所示,但您也可以通过分配整个struct来规避此问题。

// compound literal, C99 feature
p = (person) {.age = 25, .name = "Jane", .surname = p.surname};

在实践中,将字符串作为char*size_t捆绑在一个struct中是很有用的,因此通常也可以对单个字符串这样做。

tzdcorbm

tzdcorbm6#

下面是一个如何实现安全字符串赋值的示例。如果字符串比目标数组长,则Assert失败,程序退出。

#include <assert.h>
#include <stdio.h>
#include <string.h>

#define LEN(arr) (sizeof (arr) / sizeof (arr)[0])
#define APAR(arr) (arr), LEN(arr)

void Assign(char target[], int targetLen, const char source[], int sourceLen)
{   
    size_t srcStrLen;
    
    srcStrLen = strnlen(source, sourceLen);
    assert(targetLen > srcStrLen);
    memcpy(target, source, srcStrLen);
    target[srcStrLen] = '\0';
}

int main(void)
{
    typedef struct {
        char name[20];
        char surname[20];
        int unsigned age;
    } person;
    
    person p;
    
    Assign(APAR(p.name), APAR("Jane"));
    Assign(APAR(p.surname), APAR("Anderson"));
    p.age = 25;
    printf("Name: %s %s; Age: %d\n", p.name, p.surname, p.age);
    return 0;
}

相关问题