【问题标题】:Segmentation Fault in C dereferencing pointersC 解引用指针中的分段错误
【发布时间】:2014-09-27 15:52:23
【问题描述】:

我试图理解 C 编程中的指针和字符数组。 我有一个名为 player 的结构,定义如下:

typedef struct player{
    char* name;
    float ppm;
} player;

我在 Main 中有以下内容:

int main()
{
  player* head = (player*) malloc(sizeof(player));
  char* meName = &(head->name);
  (*head).name = "potato";
  (*head).name = "Paul";
  (*head).ppg =7.6;
  //printf("player is named %s\n", *meName); //First print
  printf("player is named %s\n", meName); //second print
  printf("player is named %s\n", (*head).name); //third print
  return 0;
}

为什么我的第一个打印会导致分段错误,为什么我的其他两个打印会输出以下内容:

player is named k0@
player is named Paul

如果我没记错的话meName 应该指向一个内存地址,然后通过使用另一个指向同一内存地址的指针来更改该地址。事实上,如果我像第二次打印一样打印它,它会显示该地址。当我清楚地将变量设置为指向之后立即更改的内存空间时,为什么取消引用它会导致segFault?为什么不改成Paul?为什么它首先会引发段错误?

另外,我希望能解释一下箭头 -> 与用于取消引用指针的 *。

【问题讨论】:

  • Don't cast the result of malloc (and friends)。此外,(*head).name 等价于更好的head->name。这个表达式中的括号很杂乱:&(head->name)。不管怎样,meName 的类型是 char*,而不是 char**。我的建议:编译带有完整警告的代码,并注意它们。

标签: c arrays pointers malloc


【解决方案1】:

这是不正确的,你应该得到一个警告:

char* meName = &(head->name);

meName 是指向char 的指针,而不仅仅是指向char 的指针。解决方法如下:

char** meName = &(head->name);

当然打印应该是这样的 - 即您注释掉的方式:

printf("player is named %s\n", *meName);

【讨论】:

  • 所以当我做 head->name 时,这将返回一个指向字符串“name”的指针,当我得到那个地址时通过使用与号,我得到了一个指向指针的指针。事情是这样的吗?在那种情况下,为什么 char* meName = head->name; 也不编译呢?在这种情况下,'meName' 是指向 char 的指针,head->name 也是指向char,如果我没记错的话。
  • 我根本不是要在上面使用双星号。 cmets 的格式超出了我的范围。
  • @mufasa56 -> 运算符取消引用head,为您提供指向char 的指针。 “获取地址”& 运算符使其成为指向 char 的指针。
【解决方案2】:

首先,您的所有 printfs 都使用 %s 代码。这意味着第二个参数将被解释为指向(即地址)表示字符的字节的指针。 Printf 将打印该字节的值(作为字符),然后打印内存中下一个字节的值,依此类推,直到遇到值为 0 的字节。

我会一次拿一份你的打印报表。

printf("player is named %s\n", *meName);

*meName 表示位置 meName 的字符。此字符将是介于 -128 和 +127 之间的整数。 (实际上它是 107——见下文。)现在您正在使用这个数字 107,就好像它是一个以空字符结尾的字符串(的第一个字符)的地址。这个数字很可能不是一个有效的地址,所以当printf 尝试读取该地址(107)处的字符时,它超出了您的进程的内存范围,因此您会遇到分段错误。 (通常分段错误意味着您试图在不允许您的进程访问的地址读取或写入内存。)

第二

printf("player is named %s\n", meName);

这样更好,因为 meName 至少是一个指针。那么它指向什么。初始化

meName = &(head->name);

表示meNamehead->name 字段的地址(即指向)。请注意,这实际上不是一个字符,它是一个指向字符的指针。所以发生的事情是printf 打印出head->name 的第一个字节,就好像它是一个字符一样。它恰好打印为k,因此第一个字节是107(k 的ASCII 码)。然后它打印指针的第二个字节,依此类推,直到恰好有一个字节为 00000000。为什么你的编译器接受初始化我不清楚,因为左边的类型是“指向字符的指针”,而右边的类型是“指向字符的指针”。它应该至少产生一个警告错误。

第三次打印f

printf("player is named %s\n", (*head).name);

这会查看地址head 并找到一个结构。该结构的name字段的值是五字节序列的第一个字节的地址'P''a''u''l'0。printf跟随这个指针值找到'P' (它打印),然后递增指针,并再次跟随它以找到'a',依此类推,直到找到 0 字节并在该点停止。

请注意,由于printf 有点特殊,编译器不会对其参数进行类型检查。这就是为什么即使第二个参数的类型不是 char* (如 %s 指令所指示的那样),您的第一个 printf 也已编译的原因。

【讨论】:

  • 我忘了说明 head->name(*head).name 的使用。它们的意思完全相同。大多数人更喜欢head->name,因为它更容易阅读,也更容易输入。
  • 这很有意义。所以当我想声明一个新的“字符串”时,等号左边的项目应该是一个 pointer 而右边的项目应该是实际的字符集?所以如果我说 char array[] = "Paul" 那么 array 现在是一个指向字符'P'的内存地址的指针。是吗?
  • 不完全。在大多数情况下,像"Paul" 这样的字符串文字代表一个字符数组。但在 C 中,字符数组很容易转换为指向 char 的指针。从某种意义上说,您的赋值(*head).name = "Paul" 只是(*head).name = &("Paul")[0] 的简写,这意味着该5 字符数组的第一项的地址被分配给指针head->name。但有一个例外;当用于初始化数组时,字符串字面量具有完全不同的含义:当您声明 char array[] = "Paul" ; 时,它是 char array[5] = {'P','a','u','l',0}; 的缩写。
  • 请注意:char array[]; 声明 array 是一个字符数组。但是char array[] 作为参数声明声明array 是指向char 的指针——正如我们喜欢说的那样,char*。 aC 程序员应该知道但许多人似乎从未了解过的一件事是数组和指针不是一回事。试试char array[] = "Paul" ;char *pointer = "Paul" ;。如果您将arraypointer 都传递给printf,则两者都将打印“Paul”。但是如果你同时打印sizeof,你会发现array的大小是5,pointer的大小是4或8。
【解决方案3】:
int main()
{
  player* head = (player*) malloc(sizeof(player));
  char* meName = &(head->name);
  (*head).name = "potato";
  (*head).name = "Paul";
  (*head).ppg =7.6;
  //printf("player is named %s\n", *meName); //First print
  printf("player is named %s\n", meName); //second print
  printf("player is named %s\n", (*head).name); //third print
  return 0;
}

此代码有严重的类型错误(如果您使用适当的警告级别,编译器会发现其中一些错误)并且没有使用最佳实践(并且由于拼写错误也不会编译)。对比一下

int main(void)
{
  player* head = malloc(sizeof *head);
  char** pname = &head->name;

  head->name = "potato";
  head->name = "Paul";
  head->ppm = 7.6;

  printf("address of player name is %p\n", pname);
  printf("player is named %s\n", *pname);
  printf("ditto: %s\n", head->name);

  return 0;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-04-10
    • 2013-09-18
    • 2016-02-24
    • 2021-05-25
    • 1970-01-01
    • 2010-10-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多