通过create_string_buffer、create_unicode_buffer让C语言具备修改字符串的能力
字符串的修改
我们知道C中不存在字符串这个概念,python中的字符串在C中也是通过字符数组来实现的。我们说在C中创建一个字符数组有两种方式:
char *s1 = "hello world"; char s2[] = "hello world";
这两种方式虽然打印的结果是一样的,并且s1、s2都指向了对应字符数组的首地址,但是内部的结构确是不同的。
1.char *s1 = "hello world";此时这个字符数组是存放在静态存储区里面的,程序编译的时候这块区域就已经确定好了,静态存储区在程序的整个运行期间都是存在的,主要用来存放一些静态变量、全局变量、常量。因此s1只能够访问这个字符数组,却不能够改变它,因为它是一个常量。而char s2[] = "hello world";,这种方式创建的字符数组是存放在栈当中的,可以通过s2这个指针去修改它。
2.char *s1 = "hello world";是在编译的时候就已经确定了,因为是一个常量。而char s2[] = "hello world";则是在运行时才确定。
3.char *s1 = "hello world";创建的字符数组存于静态存储区,char s2[] = "hello world";创建的字符数组存储于栈区,所以s1访问的速度没有s2快。
所以我们说char?*s
这种方式创建的字符数组在C中是不能修改的,但是我们通过ctypes却可以做到对char?*s
进行修改:
#include <stdio.h> int test(char *s1, char s2[6]) { //两种方式都进行修改 s1[0] = 'a'; s2[0] = 'a'; printf("s1 = %s, s2 = %s\n", s1, s2); }
我们还是将C文件编译成mmp.dll
import ctypes from ctypes import * lib = ctypes.CDLL("./mmp.dll") # 我们看到无论是char *s1,还是char s2[...],我们都可以使用c_char_p这种方式传递 lib.test(c_char_p(b"hello"), c_char_p(b"hello")) # s1 = aello, s2 = aello
我们看到两种方式都成功修改了,但是即便能修改,我们不建议这么做。不是说不让修改,而是应该换一种方式。如果是需要修改的话,那么不要使用c_char_p的方式来传递,而是建议通过create_string_buffer来给C语言传递可以修改字符的空间。
create_string_buffer
create_string_buffer是ctypes提供的一个函数,表示创建具有一定大小的字符缓存,就理解为字符数组即可。
from ctypes import * # 传入一个int,表示创建一个具有固定大小的字符缓存,这里是10个 s = create_string_buffer(10) # 直接打印就是一个对象 print(s) # <ctypes.c_char_Array_10 object at 0x000001E2E07667C0> # 也可以调用value方法打印它的值,可以看到什么都没有 print(s.value) # b'' # 并且它还有一个raw方法,表示C语言中的字符数组,由于长度为10,并且没有内容,所以全部是\x00,就是C语言中的\0 print(s.raw) # b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' # 还可以查看长度 print(len(s)) # 10
当然create_string_buffer如果只传一个int,那么表示创建对应长度的字符缓存。除此之外,还可以指定字节串,此时的字符缓存大小和指定的字节串大小是一致的:
from ctypes import * # 此时我们直接创建了一个字符缓存 s = create_string_buffer(b"hello") print(s) # <ctypes.c_char_Array_6 object at 0x0000021944E467C0> print(s.value) # b'hello' # 我们知道在C中,字符数组是以\0作为结束标记的,所以结尾会有一个\0,因为raw表示C中的字符数组 print(s.raw) # b'hello\x00' # 长度为6,b"hello"五个字符再加上\0一共6个 print(len(s))
当然create_string_buffer还可以指定字节串的同时,指定空间大小。
from ctypes import * # 此时我们直接创建了一个字符缓存,如果不指定容量,那么默认和对应的字符数组大小一致 # 但是我们还可以同时指定容量,记得容量要比前面的字节串的长度要大。 s = create_string_buffer(b"hello", 10) print(s) # <ctypes.c_char_Array_10 object at 0x0000019361C067C0> print(s.value) # b'hello' # 长度为10,剩余的5个显然是\0 print(s.raw) # b'hello\x00\x00\x00\x00\x00' print(len(s)) # 10
下面我们来看看如何使用create_string_buffer来传递:
#include <stdio.h> int test(char *s) { //变量的形式依旧是char *s //下面的操作就是相当于把字符数组的索引为5到11的部分换成" satori" s[5] = ' '; s[6] = 's'; s[7] = 'a'; s[8] = 't'; s[9] = 'o'; s[10] = 'r'; s[11] = 'i'; printf("s = %s\n", s); }
from ctypes import * lib = CDLL("./mmp.dll") s = create_string_buffer(b"hello", 20) lib.test(s) # s = hello satori
此时就成功地修改了,我们这里的b"hello"占五个字节,下一个正好是索引为5的地方,然后把索引为5到11的部分换成对应的字符。但是需要注意的是,一定要小心\0
,我们知道C语言中一旦遇到了\0
就表示这个字符数组结束了。
from ctypes import * lib = CDLL("./mmp.dll") # 这里把"hello"换成"hell",看看会发生什么 s = create_string_buffer(b"hell", 20) lib.test(s) # s = hell # 我们看到这里只打印了"hell",这是为什么? # 我们看一下这个s print(s.raw) # b'hell\x00 satori\x00\x00\x00\x00\x00\x00\x00\x00' # 我们看到这个create_string_buffer返回的对象是可变的,在将s传进去之后被修改了 # 如果没有传递的话,我们知道它是长这样的。 """ b'hell\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' hell的后面全部是C语言中的\0 修改之后变成了这样 b'hell\x00 satori\x00\x00\x00\x00\x00\x00\x00\x00' 我们看到确实是把索引为5到11(包含11)的部分变成了"satori" 但是我们知道C语言中扫描字符数组的时候一旦遇到了\0,就表示结束了,而hell后面就是\0, 因为即便后面还有内容也不会输出了,所以直接就只打印了hell """
另外除了create_string_buffer之外,还有一个create_unicode_buffer,针对于wchar_t,用法和create_string_buffer一样。
C语言中查看字符数组的长度
C语言中如何查看字符数组的长度呢?有两种方法,一种是通过sizeof,一种是通过strlen。话说我说这个干什么?算了,不管了。
#include <stdio.h> #include <string.h> int main() { char s[] = "hello world"; //C语言中查看字符串的长度可以使用strlen,这个需要导入string.h头文件。strlen计算的就是字符的个数,不包括\0 //使用sizeof计算的结果是包含\0的,所以会比strlen计算的结果多1 printf("%d %d\n", strlen(s), sizeof(s) / sizeof(s[0])); // 11 12 return 0; }
但是我们发现字符数组的创建方式是通过char s[]
这种形式,如果是char?*s
呢?
#include <stdio.h> #include <string.h> int main() { char *s = "hello world"; printf("%d %d\n", strlen(s), sizeof(s) / sizeof(s[0])); // 11 8 return 0; }
我们看到使用strlen计算的结果是一样的,但是使用sizeof得到的结果却是不一样的。因为char?*s
,这个s我们虽然可以使用它来打印字符数组,但它本质上是一个指针,一个指针在64位机器上占8个字节,所以结果是8。而char?s[]
中的s虽然也指向字符数组的首地址,但它本质上是一个数组名,我们使用sizeof查看得到的结果还是字符数组中所有元素的总大小。
艾玛,你扯到C上面干啥。。。。。。你又不会C。。。。。。