我必须承认我不是 GDK 方面的专家,但我已经使用 C 多年,而且我使用 C++ 多年,顺便说一句。几年前,我已经使用 gtkmm(GTK+ 的 C++ 绑定)进行了编程。所以,我觉得能够理清对 OP 来说可能看起来令人困惑的地方。
C 字符串
过去,字符串的处理是一些不同方法的主题。
例如,在 PASCAL 中,字符串始终是 256 字节的数组。第一个字节被保留用于字符串的长度,resp。包含字符的其他字节数。这是一个安全的解决方案,但它也有缺点:即使是最短的字符串也总是消耗 256 个字节。更糟糕的是,不可能有超过 255 个字符的字符串。在这种情况下,有一些变通方法可以制作字符串列表,但这实际上很烦人。
在 C 中,它的解决方式不同。字符串可以有任意长度(只要它们适合计算机的内存)。长度本身不被存储。取而代之的是,字符串的结尾由一个特殊字符'\0'
(值为 0 的字节)标记,该字符专门为此目的而保留。缺点是:字符串的长度必须单独存储,或者必须计算字符直到第一次出现'\0'
. C 标准库为此提供了一个现成的函数:strlen()。这使得可以通过字符串的第一个字符的地址来处理字符串。因此,C 字符串由 a 处理char*
(或者const char*
如果 C 字符串不能被修改,则由 a 处理)。
C 库提供了一些附加函数来支持使用 C 字符串,例如strcpy()。strcpy()
将源指针(第 2 个参数)中的连续字节复制到目标指针(第 1 个参数),直到该'\0'
字节出现。(它也被复制了,但函数随后结束。)
二进制数据
二进制数据(由具有任意值的字节组成)可以像 C 字符串一样处理。char
是一个整数类型,大小为 1 个字节。因此,它也是合适的候选人。但是,二进制数据可能在任何位置包含任何可能的值 0 … 255。所以,用结尾的原则是'\0'
行不通的。相反,长度必须始终单独存储。
对于处理二进制数据,unsigned char
通常是首选。恕我直言,这有两个根本原因:
- 它可以帮助程序员区分 C 字符串和指向任意二进制数据的指针。
char
可能(根据 C 标准和 C++ 标准)有符号或无符号(取决于编译器供应商的决定)。如果一个char
值的符号是一个问题,signed char
或者unsigned char
必须被使用。对于处理二进制数据字节,显式处理它们通常更方便unsigned
。
标准 C 库提供相应的。处理二进制数据的函数,例如memcpy()。请注意,它memcpy()
提供了第三个参数来定义从源指针复制到目标指针的字节大小。
贮存
除了 C 字符串的优点之外,它们还带来了负担:程序员负责始终提供足够的存储空间。在 C 中,有多种可能性:
- 使用带有
char
数组的全局变量,例如static char data[1024];
- 使用带有
char
数组的局部变量(在函数中),例如char data[1024];
- 在堆上分配内存,例如
char *data = malloc(1024);
。
字符数组的大小必须在程序中定义(在编译时)。在程序运行时无法更改此设置。(一个例外是可变长度数组。根据 C99 标准,它们是可选功能,但即使在最近的 C++ 标准中也没有这样的东西,尽管一些 C++ 编译器将它们作为专有扩展提供。)如果存储的大小是在运行前未知,动态内存分配是唯一的解决方案(即size_t n = somehowDetermined(); char *data = malloc(n);
)。
管理足够的存储空间听起来实际上并没有那么复杂,但正确并始终正确地组织它是多年来 C 和 C++ 程序中的基本问题之一。(C++ 从 C 继承了这个问题。添加了一个new
运算符和delete
运算符以允许在堆上进行类型安全分配,但实际上这并没有太大帮助。)因此,C++ 标准委员会多年来投入了大量资金来进行更安全的替换。
标准::字符串
在 C++ 中,字符串可能存储为std::string
. 它使使用字符串的生活变得更加轻松。例如,虽然 C 字符串必须与strcmp()
或类似的东西进行比较,但 C++std::string
提供了一个重载operator==()
,它允许直观的代码,例如std::string text = input(); if (text == "exit") exit();
。另一个重要的优点std::string
是内部内存管理。字符串可以添加到字符串中,插入到字符串中等等,并且std::string
会关心内部存储的正确分配。
此外,astd::string
在内部存储其内容的大小。(作者可能发现将额外的字节用于另一个积分是值得的,这样就不必计算任何字符串长度检索的字符数。)这也std::string
为二进制数据提供了足够的容器。
为了与 C API 兼容,std::string
提供了一个“后门” std::string::c_str()。它提供std::string
as C 字符串的原始内容。它允许返回的 C 字符串'\0'
在最后一个字符之后有一个字节,即std::string::c_str()[std::string::size()]
必须返回'\0'
。还有一个std::string::data()函数可以访问std::string
. 在 C++11 之前,只有std::string::c_str()
必须授予终止 0 而不是std::string::data()
. 在 C++11 中,这种情况发生了变化。std::string::data()
现在,和的返回值不能有任何区别std::string::c_str()
——这两个函数都只是返回指向内部存储的原始数据的指针。因此,std::string
有效地必须放置一个'\0'
最后的字符总是不管内容。这可能看起来像是一种浪费,但实际上,我们谈论的是一个额外的字节,这是一个很小的代价,却为代码的健壮性带来了很大的优势。
操作代码
考虑到 OP 想要从内存中加载图像文件(通常由任意字节组成),下面的代码是错误的:
std::string data;
// image file somehow read in
guchar* pixdata = new guchar[data.size()+1];// creating a guchar* with space for image data
strcpy((char*)pixdata,data.c_str());// copying data from string to the guchar*
对于任意二进制数据,strcpy()
是错误的。它复制直到找到第一个 0 字节。任何地方的图像数据中都可能有 0 个字节(例如,如果它包含黑色像素)。strcpy()
因此,复制太少字节的可能性很高。在这种情况下,memcpy()
将是更好的选择。
实际上,两者都不是必需的。
std::string data;
已经包含必须输入的所有内容、gdk_pixbuf_loader_write()
指向原始数据的指针和大小。因此,我建议完全删除new[]
anddelete
东西并用以下内容替换它:
std::string data;
// image file somehow read in
GdkPixbufLoader* loader = gdk_pixbuf_loader_new();
gdk_pixbuf_loader_write(loader, (const guchar*)data.data(), data.size(), nullptr);
对于最后一行,我也可以使用:
gdk_pixbuf_loader_write(loader, (const guchar*)data.c_str(), data.size(), nullptr);
正如我已经解释过的,这在 C++11 之后没有任何区别。我使用data.data()
它看起来更好(考虑到它的内容std::string
是二进制数据而不是 C 字符串的事实)。
关于演员表的注释const guchar*
:
std::string
在内部存储一个动态分配的数组char
。因此,std::string::data()
返回const char*
(或char*
)。gdk_pixbuf_loader_write ()需要一个const guchar*
作为第二个参数。
guchar只是一个
typedef unsigned char guchar;
因此,const char*
被转换为const unsigned char*
。指针类型转换是应该小心完成的。(一般来说,它们是可能被设计破坏并承担未定义行为的危险的最后手段- 每个 C 和 C++ 程序员的瘟疫。)在这种情况下,转换是安全的,并且根据C++ 标准。我找到了另一个答案,详细解释了这一点:SO:我可以将 unsigned char 转换为 char 吗?反之亦然?.
OP 尝试修复代码
在我花了一些提示之后,OP 建议进行以下修复:
string data = getFileInMem("0.0.0.0:8000/test.txt");
guchar* pixdata = (const guchar*)data.data();
GdkPixbufLoader* loader = gdk_pixbuf_loader_new();
gdk_pixbuf_loader_write(loader,pixdata,sizeof(pixdata),nullptr);
不幸的是,这个解决方案引入了一个新错误:sizeof(pixdata)
. 虽然data.size()
返回 中字符串长度的大小,但此处data
的sizeof(pixdata)
运算符是错误的选择。
sizeof是一个运算符,它总是在编译时解析——返回它右侧的类型的大小。可以用类型或表达式调用它:
std::cout << sizeof (char) << std::endl;
char c;
std::cout << sizeof c << std::endl;
将输出:
1
1
因此,表达式甚至不需要在运行时有效存储,因为sizeof
它总是在编译时解析并基于结果表达式的类型:
char *c = nullptr;
std::cout << sizeof *c << std::endl;
将输出:
1
这可能令人惊讶,因为*c
看起来像是访问空指针的内容(通常是未定义的行为)。在这种情况下,它实际上不是。由于sizeof
运算符在编译时评估类型,生成的代码只包含此评估的结果。因此,*c
运行时不会发生任何事情,代码中也不会出现未定义的行为。
但是,sizeof pixdata
不返回的大小data
而只是指针的大小guchar*
。如果 OP 在 32 位平台上编译,它可能是 4,而对于 64 位平台,它可能是 8——但对于某个平台,它总是相同的值。
所以,要解决这个问题,它必须是:
string data = getFileInMem("0.0.0.0:8000/test.txt");
const guchar* pixdata = (const guchar*)data.data();
GdkPixbufLoader* loader = gdk_pixbuf_loader_new();
gdk_pixbuf_loader_write(loader, pixdata, data.size(), nullptr);
或者
string data = getFileInMem("0.0.0.0:8000/test.txt");
const guchar* pixdata = (const guchar*)data.data();
gsize pixdatasize = (gsize)data.size();
GdkPixbufLoader* loader = gdk_pixbuf_loader_new();
gdk_pixbuf_loader_write(loader, pixdata, pixdatasize, nullptr);
这变成了一个很长的答案。这可能说明即使是某些 C++ 代码行也需要大量背景知识才能正确编写它们。因此,入门级程序员经常被暗示要获得一本好的 C++ 书是有道理的。我不会坚持认为不可能以另一种方式学习 C++。但是,恕我直言,一本好的 C++ 书值得考虑。C++ 中有很多陷阱,其中大部分是从 C 继承而来的,其中一些是专门在 C++ 本身中引入的。