为什么这个指针操作会导致段错误?

4
我似乎已经达到了我的“指针功夫”的极限,现在请求帮助(或者某种大脑药物)。
项目的大致概述:一个嵌入式ARM视频编码器板运行Linux,使用制造商提供的文档不全、支持较差的SDK。在其庞大的代码中,有一大堆是由gSoap从某个WSDL生成的,正是这个导致了头疼。
在由gSoap自动生成的一个巨大数据结构的一部分中,我们有一个地方可以写入一些数据(或者一个地方可以写入指向我们已经写入一些数据的指针):
 struct tt__IPAddress
 {
    enum tt__IPType Type;   /* required element of type tt:IPType */
    char *IPv4Address;  /* optional element of type tt:IPv4Address */
    char *IPv6Address;  /* optional element of type tt:IPv6Address */
 };

然后我们有这段代码,简单来说,应该将一个字符串写入IPv4地址:
DNSInformation->DNSManual = ((struct tt__IPAddress *)soap_malloc(soap, sizeof(struct tt__IPAddress)));
DNSInformation->DNSManual->IPv4Address = (char **)soap_malloc(soap, sizeof(char *));
DNSInformation->DNSManual->IPv4Address[0] = (char *)soap_malloc(soap, sizeof(char) * LARGE_INFO_LENGTH);
// Code crashes at this next line:
strncpy(*DNSInformation->DNSManual->IPv4Address, dns_string, LARGE_INFO_LENGTH-1);

dns_string是您期望的内容——比如"192.168.2.254"。它已正确地加上了空终止符,LARGE_INFO_LENGTH的值很大(例如1024),所以该字符串有充足的空间。我为了安全性从strcpy()更改为strncpy()。

我的背景是较小的嵌入式系统(没有操作系统,不使用malloc()),所以我有些困难,无法确信我理解这段代码在做什么。这段代码是自动生成的/SDK的一部分,不是我的创作,也没有注释。

以下是我认为它在做什么:

DNSInformation->DNSManual = ((struct tt__IPAddress *)soap_malloc(soap, sizeof(struct tt__IPAddress)));

分配一块内存,并将其指向DNSManual,这里将存放tt__IPAddress结构。

DNSInformation->DNSManual->IPv4Address = (char **)soap_malloc(soap, sizeof(char *));

分配一块RAM,指向IPv4Address,其中将写入包含地址的字符串指针。

DNSInformation->DNSManual->IPv4Address[0] = (char *)soap_malloc(soap, sizeof(char) * LARGE_INFO_LENGTH);

现在这段代码有点困扰我,它似乎试图分配内存来保存IPv4Address[0]指向的字符串,但是这看起来可能是一个(32位)指针指向char类型。
这段代码之前可以工作,但在其他地方进行了一些更改后,现在总是在strncpy()处崩溃。
我的问题有两个:
1. 有人能帮我正确理解malloc和指针吗? 2. 如何追踪/调试这个问题?
不幸的是,我们在这个设置中没有GDB设施 - 是的,我确定设置它是可能的,但现在让我们假设由于许多无聊和繁琐的原因而不实际。
目前,在这个小片段的每一行中都散布着调试printf语句,事实上它总是在strncpy()行停止并出现SIGSEGV。
编辑关闭,因为WhozCraig已经找到了答案:
由于某种原因,gSoap已更改结构tt__IPAddress,也许它已经用完了星号,但它以前是什么,它应该是这样的:
struct tt__IPAddress
 {
    enum tt__IPType Type; 
    char **IPv4Address;  /* note ptr to ptr */
    char **IPv6Address;
 };

5
首先,显而易见的错误是你在强制转换malloc()的返回值。 - user529758
1
@H2CO3 欢迎来到讲坛,感谢您的链接。 :) - unwind
代码并不是非常一致,对吧?编译器应该会为将(char*)转换为(char)的隐式转换发出警告。结构体的声明肯定是错的! - Nicholas Wilson
缺乏更多上下文,很难说哪个是错误的,结构定义还是发布的代码。IPvAddress应该是char **,或者稍后的代码不应该进行sizeof(char)分配,并且应将该字段视为指向字符串而不是指向指针表的指针。 - Nicholas Wilson
1
@H2CO3 我确定我有,不是故意听起来好像我不重视你之前的努力。当然,我同意,人们发布带有转换的代码的频率令人感到惊恐。 :| - unwind
显示剩余5条评论
4个回答

2
代码不遵循结构布局。 布局如下:
 struct tt__IPAddress
 {
    enum tt__IPType Type;   /* required element of type tt:IPType */
    char *IPv4Address;  /* optional element of type tt:IPv4Address */
    char *IPv6Address;  /* optional element of type tt:IPv6Address */
 };

意思: IPv4Address 是一个char指针。然而这个:

DNSInformation->DNSManual->IPv4Address = (char **)soap_malloc(soap, sizeof(char *));

这是将 char ** 强制类型转换成 char *,但是 类型 仍然是 char *,所以:

strncpy(*DNSInformation->DNSManual->IPv4Address, dns_string, LARGE_INFO_LENGTH-1);

这里要将指针解除引用,然后将其转换为单个字符char,我可以向您保证,在您的平台上(以及其他任何平台),这与char *不兼容。

至少应该有警告在编译期间运行,如果您的编译器有任何头脑,就会有明显的错误。这似乎是最初意图如下:

 struct tt__IPAddress
 {
    enum tt__IPType Type; 
    char **IPv4Address;  /* note ptr to ptr */
    char **IPv6Address;
 };

如果有一个动态指针数组,每个指针都是为单个IP地址分配的动态内存,那么这将更加合理。也就是说,如果您只打算在结构中使用一个IPv4地址,则应进行更改:

DNSInformation->DNSManual = soap_malloc(soap, sizeof(struct tt__IPAddress)));
if (DNSInformation->DNSManual)
{
    DNSInformation->DNSManual->IPv4Address = soap_malloc(soap, sizeof(char) * LARGE_INFO_LENGTH);
    if (DNSInformation->DNSManual->IPv4Address)
    {
        strncpy(DNSInformation->DNSManual->IPv4Address, dns_string, LARGE_INFO_LENGTH-1);
        DNSInformation->DNSManual->IPv4Address[LARGE_INFO_LENGTH-1] = 0;
    }
}

或类似于此的内容。

您说得非常准确!之前检查自动生成的代码的早期版本时发现确实有两个星号。编译器警告虽然已经存在,但即使在SDK“工作”时也会生成约10,000个警告,所以它们在噪声中有些被忽略了。为什么gSoap突然决定去掉一个星号还不清楚,但现在我知道要密切关注它了。非常感谢! - John U
@JohnU 一万个警告?哎呀。我会认真考虑寻找补充说明或直接替换的方案。正如你所看到的,它们通常意味着“我不确定这是一个好主意;你真的想这样做吗?”无论如何,我很高兴能帮到你。 - WhozCraig
它很大,不幸的是,这是唯一一个包含我们客户所需功能的东西,而又不需要在预算末尾加上额外的零或三来雇用一支程序员团队。打开编译器的冗长模式会输出一百万行文本 - 我已经开始写解析器来捕捉其中的内容了! - John U

0

我认为它看起来有问题。

这个:

char *IPv4Address;  /* optional element of type tt:IPv4Address */

IPv4Address是指向字符数据的单个指针,即一个字符串。

但是它被用作这样:

DNSInformation->DNSManual->IPv4Address = (char **)soap_malloc(soap,
                                                              sizeof(char *));

这是错误的。假设soap_malloc()有一个合理的返回值(即void *以符合malloc()),则不需要进行任何转换,但是强制转换与实际类型不同的事实表明存在某种错误。

它将IPv4Address结构字段视为指向指针的指针,而它显然不是。


0

我相信它应该看起来类似于这样:

DNSInformation->DNSManual = soap_malloc(soap, sizeof(struct tt__IPAddress)));
DNSInformation->DNSManual->IPv4Address = soap_malloc(soap, sizeof(char) * LARGE_INFO_LENGTH);

strncpy(DNSInformation->DNSManual->IPv4Address, dns_string, LARGE_INFO_LENGTH-1);

你的结构体包含指向字符串的指针,但首先它会分配一个指针数组(char**),然后为该数组中的第一个指针分配内存。

使用strncpy()后不要忘记设置二进制零,因为它本身不会设置。

//编辑:第一部分是错误的,抱歉


1
*xx[0]是相同的。 - Shahbaz
你说得对,谢谢你的提示。我没有认真考虑过。 - robin.koch
语句“****xx[0]是相同的”的真实性取决于变量x所处的生命周期阶段。只有在x被创建后才为真。在创建之前,以int x[0];的形式声明x***是无效的。但是,声明int *a;是有效的。 - ryyker

0

这里有一个工作的解决方案(我使用了malloc而不是soap_malloc等):

#include <stdio.h>
#include <stdlib.h>

#define LARGE_INFO_LENGTH 1024

enum tt__IPType { tt__IPv4, tt__IPv6 };

struct tt__IPAddress
{
  enum tt__IPType Type;   /* required element of type tt:IPType */
  char *IPv4Address;  /* optional element of type tt:IPv4Address */
  char *IPv6Address;  /* optional element of type tt:IPv6Address */
};

struct tt__DNSInformation
{
  struct tt__IPAddress* DNSManual;
};

int main()
{
  struct tt__DNSInformation* DNSInformation;
  char dns_string[] = "192.168.2.254";

  DNSInformation = malloc(sizeof(struct tt__DNSInformation));
  DNSInformation->DNSManual = malloc(sizeof(struct tt__IPAddress));
  DNSInformation->DNSManual->IPv4Address = malloc(sizeof(char) * LARGE_INFO_LENGTH);
  strncpy(DNSInformation->DNSManual->IPv4Address, dns_string, LARGE_INFO_LENGTH - 1);

  printf("%s\n", DNSInformation->DNSManual->IPv4Address);
  return 0;
}

附注:与“type *pointer =(type *)malloc(size * sizeof(type))”相比,“type pointer = malloc(size * sizeof( pointer))”更短,更少出错,更具未来性,更易于维护,并避免了重复。 - Shahbaz
@Shahbaz 谢谢,我已经移除了强制类型转换。 - kol

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接