尝试使用 GNU 的 GMP 库中的类型作为 Bison 的 yylval 类型时出错
Error when attempting to use a type from GNU's GMP library as the yylval type for Bison
我试图通过在 Bison 文件中包含以下内容来使用 GMP 库中的类型 mpz_t
作为 yylval
的类型:
%define api.value.type {mpz_t}
我检查了生成的解析器,它正确地生成了 typedef mpz_t YYSTYPE
行,YYSTYPE
后来被用来创建 yylval
.
mpz_t
在 GMP 头文件 gmp.h
中的类型定义为 typedef __mpz_struct mpz_t[1];
。反过来,__mpz_struct
被定义为
typedef struct
{
// struct members here - don't believe they're important
} __mpz_struct;
Bison 运行没有错误,但每当我尝试创建可执行文件时,我都会收到以下错误:
calc.tab.c: In function ‘yyparse’:
calc.tab.c:1148:12: error:
incompatible types when assigning to type ‘YYSTYPE’ from type
‘struct __mpz_struct *’
*++yyvsp = yylval;
yyvsp
被定义为指向 YYSTYPE
.
的指针
知道如何解决这个问题吗?
如您所说,mpz_t
被定义为数组类型的别名:
typedef __mpz_struct mpz_t[1];
因此,对 mpz_t
类型变量的赋值是非法的:
mpz_t a, b;
mpz_init(b);
a = b; /* Error: incompatible types when assigning to type ‘mpz_t’ */
/* from type ‘struct __mpz_struct *’ */
相反,有必要使用内置赋值函数之一:
mpz_t a, b;
mpz_inits(a, b, 0);
mpz_set(a, b); /* a is now a copy of b */
由于 gmp 管理内存的方式,禁止直接分配给 mpz_t
是必要的。请参阅下面的注释 1。
Bison假设语义类型YYSTYPE
可以赋值(见注2),也就是说不能是数组类型。这通常不是问题,因为通常 YYSTYPE
是联合类型,并且可以分配给具有数组成员的联合。因此,只要将数组类型包含在 %union
声明中,就可以将数组类型与 bison 一起使用。
但是你不能用 gmp 这样做,因为虽然它可以编译,但它不会工作。您最终会遇到大量泄漏的内存,并且很可能会遇到隐蔽的错误,其中 gmp 计算出错误的值(或者以更明显的方式失败,例如 free
从 mpz_t
).
直接使用mpz_t
对象作为语义值是可能的,但这并不容易。您最终会花费大量时间思考哪些堆栈槽具有已初始化的语义值;哪些具有需要 mpz_clear
ed 的值,以及许多其他令人不安的细节。
一个更简单(但不简单)的解决方案是使语义值成为指向的mpz_t
指针。如果您只是制作一个 bignum 计算器,您可以完全绕过语义值并维护您自己的值堆栈。只要每个缩减操作都从值堆栈中弹出所有参数并推送其结果,这就会成功。
这个值堆栈也是 mpz_t
个值的向量,但它在几个重要方面与解析器堆栈不同,因为它完全在您的控制之下:
您没有义务创造 bison 需要创造的临时价值(见注释 2)。例如,如果你想做一个加法,这会将两个操作数从堆栈中弹出并将结果推回,你可以这样做:
mpz_add(val_stack[top - 2], val_stack[top - 2], val_stack[top - 1]);
--top;
可以在解析前初始化值栈,解析完成后清空所有元素。这使内存管理变得更加简单,并允许您重用分配的肢体向量。
像运算符和括号这样没有关联语义值的标记不会在值堆栈上占据space。这并没有节省多少space,但它避免了初始化和清除其中从来没有有用数据的堆栈槽的需要。
备注
1。为什么 GMP 不鼓励直接赋值
根据 gmp 手册,制作大小为 1 的 mpz_t
(和其他类似类型)数组只是为了弥补 C 缺乏传递引用。由于数组在用作函数参数时会衰减为指针,因此您无需显式标记参数即可获得按引用传递。但是一定有人想到使用数组类型也可以防止直接赋值给 mpz_t
。由于 gmp 管理内存的方式,直接分配无法工作。
Gmp 值必须包含对已分配存储空间的引用。 (必然的,因为bignum的大小没有限制,所以不同的bignum是不同的大小。)一般来说,这样管理对象有两种方式:
使对象不可变。然后就可以随便分享了,因为不能修改。
始终在分配时复制对象,从而无法共享。然后可以在不影响任何其他对象的情况下修改对象。
例如,Java 和 C++ 字符串方法就是这两种策略的例证。不幸的是,这两种策略都依赖于语言中的一些基础设施:
不可变字符串需要进行垃圾回收。如果没有垃圾收集器,就无法判断何时可以释放字符串的存储空间。可以对内存分配进行引用计数,但引用计数需要递增和递减,除非您准备让您的代码成为引用计数维护的沼泽,否则您需要一些语言支持。
复制字符串需要覆盖赋值运算符。这在 C++ 中是可能的,但很少有其他语言能如此灵活。
上述两种策略都存在性能问题。
不可变对象修改时需要复制,可以将简单的线性复杂度变成二次复杂度。这是一个众所周知的问题,重复附加到 Java 或 Python 字符串; Java 的 StringBuilder 旨在弥补这个问题。不可变整数会很烦人;累加和很常见,例如 (sum += value;
),并且每次都必须通过这样的循环复制 sum
可能会大大减慢循环速度。
另一方面,在赋值时强制复制使得无法共享常量,甚至无法重新排列向量。这会导致大量额外的复制,再次导致线性算法变成二次算法。
Gmp 选择了可变对象策略。 Bignums 必须 在赋值时复制,并且由于 C 不允许覆盖赋值运算符,最简单的解决方案是禁止使用赋值运算符,强制使用库函数。
由于在某些情况下移动大数而不进行复制很有用——例如,混洗大数数组——gmp 还提供了交换功能。而且,如果你非常非常小心并且比我更了解 gmp 的内部结构,则可能只使用上面提到的 union
hack,或者使用 memcpy()
,以便做更多gmp 对象的复杂重新排列,前提是您保持重要的不变性:
肢体的每个矢量必须被一个且仅一个 mpz_t
对象引用。
重要的原因是 gmp 将在必要时使用 realloc 调整 bignum 的大小。假设 a
和 b
是 mpz_t
,我们使用一些 hack 使它们成为相同的 bignum,共享内存:
memcpy(a, b, sizeof(a));
现在,我们让 b
变得更大:
mpz_mul(b, b, b); /* Set b to b squared */
这会工作得很好,但在内部它会做类似
的事情
tmp = realloc(b->_mp_d, 2 * b->_mp_size);
if (tmp) b->_mp_d = tmp;
为了使 b
足够大以容纳结果。这对 b
来说很好用,但它可能会导致 a
指向的肢体进入边缘状态,因为分配新存储的成功 realloc
将自动释放旧存储。
任何增加 b
大小的操作都会发生同样的事情;就地平方只是一个例子。 a
在几乎任何增加 b
大小的修改之后都可能以悬空指针结束: mpz_add(b, tmp1, tmp2);
(假设 tmp1
and/or tmp2
是大于 b
.)
2。为什么 Bison 要求语义值是可分配的
Bison 为每次归约创建一个临时 YYSTYPE
对象;这个临时变量是 bison 操作中表示为 $$
的实际变量。在执行缩减操作之前,解析器执行 $$ = ;
的等价物。一旦动作完成,</code> 到 <code>$n
被弹出堆栈,$$
被压入堆栈。实际上,这会用 $$
覆盖旧的 </code>,这就是必须使用临时变量的原因。 (否则,在操作中设置 <code>$$
会意外地使
无效。)
我试图通过在 Bison 文件中包含以下内容来使用 GMP 库中的类型 mpz_t
作为 yylval
的类型:
%define api.value.type {mpz_t}
我检查了生成的解析器,它正确地生成了 typedef mpz_t YYSTYPE
行,YYSTYPE
后来被用来创建 yylval
.
mpz_t
在 GMP 头文件 gmp.h
中的类型定义为 typedef __mpz_struct mpz_t[1];
。反过来,__mpz_struct
被定义为
typedef struct
{
// struct members here - don't believe they're important
} __mpz_struct;
Bison 运行没有错误,但每当我尝试创建可执行文件时,我都会收到以下错误:
calc.tab.c: In function ‘yyparse’:
calc.tab.c:1148:12: error: incompatible types when assigning to type ‘YYSTYPE’ from type ‘struct __mpz_struct *’
*++yyvsp = yylval;
yyvsp
被定义为指向 YYSTYPE
.
知道如何解决这个问题吗?
如您所说,mpz_t
被定义为数组类型的别名:
typedef __mpz_struct mpz_t[1];
因此,对 mpz_t
类型变量的赋值是非法的:
mpz_t a, b;
mpz_init(b);
a = b; /* Error: incompatible types when assigning to type ‘mpz_t’ */
/* from type ‘struct __mpz_struct *’ */
相反,有必要使用内置赋值函数之一:
mpz_t a, b;
mpz_inits(a, b, 0);
mpz_set(a, b); /* a is now a copy of b */
由于 gmp 管理内存的方式,禁止直接分配给 mpz_t
是必要的。请参阅下面的注释 1。
Bison假设语义类型YYSTYPE
可以赋值(见注2),也就是说不能是数组类型。这通常不是问题,因为通常 YYSTYPE
是联合类型,并且可以分配给具有数组成员的联合。因此,只要将数组类型包含在 %union
声明中,就可以将数组类型与 bison 一起使用。
但是你不能用 gmp 这样做,因为虽然它可以编译,但它不会工作。您最终会遇到大量泄漏的内存,并且很可能会遇到隐蔽的错误,其中 gmp 计算出错误的值(或者以更明显的方式失败,例如 free
从 mpz_t
).
直接使用mpz_t
对象作为语义值是可能的,但这并不容易。您最终会花费大量时间思考哪些堆栈槽具有已初始化的语义值;哪些具有需要 mpz_clear
ed 的值,以及许多其他令人不安的细节。
一个更简单(但不简单)的解决方案是使语义值成为指向的mpz_t
指针。如果您只是制作一个 bignum 计算器,您可以完全绕过语义值并维护您自己的值堆栈。只要每个缩减操作都从值堆栈中弹出所有参数并推送其结果,这就会成功。
这个值堆栈也是 mpz_t
个值的向量,但它在几个重要方面与解析器堆栈不同,因为它完全在您的控制之下:
您没有义务创造 bison 需要创造的临时价值(见注释 2)。例如,如果你想做一个加法,这会将两个操作数从堆栈中弹出并将结果推回,你可以这样做:
mpz_add(val_stack[top - 2], val_stack[top - 2], val_stack[top - 1]); --top;
可以在解析前初始化值栈,解析完成后清空所有元素。这使内存管理变得更加简单,并允许您重用分配的肢体向量。
像运算符和括号这样没有关联语义值的标记不会在值堆栈上占据space。这并没有节省多少space,但它避免了初始化和清除其中从来没有有用数据的堆栈槽的需要。
备注
1。为什么 GMP 不鼓励直接赋值
根据 gmp 手册,制作大小为 1 的 mpz_t
(和其他类似类型)数组只是为了弥补 C 缺乏传递引用。由于数组在用作函数参数时会衰减为指针,因此您无需显式标记参数即可获得按引用传递。但是一定有人想到使用数组类型也可以防止直接赋值给 mpz_t
。由于 gmp 管理内存的方式,直接分配无法工作。
Gmp 值必须包含对已分配存储空间的引用。 (必然的,因为bignum的大小没有限制,所以不同的bignum是不同的大小。)一般来说,这样管理对象有两种方式:
使对象不可变。然后就可以随便分享了,因为不能修改。
始终在分配时复制对象,从而无法共享。然后可以在不影响任何其他对象的情况下修改对象。
例如,Java 和 C++ 字符串方法就是这两种策略的例证。不幸的是,这两种策略都依赖于语言中的一些基础设施:
不可变字符串需要进行垃圾回收。如果没有垃圾收集器,就无法判断何时可以释放字符串的存储空间。可以对内存分配进行引用计数,但引用计数需要递增和递减,除非您准备让您的代码成为引用计数维护的沼泽,否则您需要一些语言支持。
复制字符串需要覆盖赋值运算符。这在 C++ 中是可能的,但很少有其他语言能如此灵活。
上述两种策略都存在性能问题。
不可变对象修改时需要复制,可以将简单的线性复杂度变成二次复杂度。这是一个众所周知的问题,重复附加到 Java 或 Python 字符串; Java 的 StringBuilder 旨在弥补这个问题。不可变整数会很烦人;累加和很常见,例如 (
sum += value;
),并且每次都必须通过这样的循环复制sum
可能会大大减慢循环速度。另一方面,在赋值时强制复制使得无法共享常量,甚至无法重新排列向量。这会导致大量额外的复制,再次导致线性算法变成二次算法。
Gmp 选择了可变对象策略。 Bignums 必须 在赋值时复制,并且由于 C 不允许覆盖赋值运算符,最简单的解决方案是禁止使用赋值运算符,强制使用库函数。
由于在某些情况下移动大数而不进行复制很有用——例如,混洗大数数组——gmp 还提供了交换功能。而且,如果你非常非常小心并且比我更了解 gmp 的内部结构,则可能只使用上面提到的 union
hack,或者使用 memcpy()
,以便做更多gmp 对象的复杂重新排列,前提是您保持重要的不变性:
肢体的每个矢量必须被一个且仅一个 mpz_t
对象引用。
重要的原因是 gmp 将在必要时使用 realloc 调整 bignum 的大小。假设 a
和 b
是 mpz_t
,我们使用一些 hack 使它们成为相同的 bignum,共享内存:
memcpy(a, b, sizeof(a));
现在,我们让 b
变得更大:
mpz_mul(b, b, b); /* Set b to b squared */
这会工作得很好,但在内部它会做类似
的事情tmp = realloc(b->_mp_d, 2 * b->_mp_size);
if (tmp) b->_mp_d = tmp;
为了使 b
足够大以容纳结果。这对 b
来说很好用,但它可能会导致 a
指向的肢体进入边缘状态,因为分配新存储的成功 realloc
将自动释放旧存储。
任何增加 b
大小的操作都会发生同样的事情;就地平方只是一个例子。 a
在几乎任何增加 b
大小的修改之后都可能以悬空指针结束: mpz_add(b, tmp1, tmp2);
(假设 tmp1
and/or tmp2
是大于 b
.)
2。为什么 Bison 要求语义值是可分配的
Bison 为每次归约创建一个临时 YYSTYPE
对象;这个临时变量是 bison 操作中表示为 $$
的实际变量。在执行缩减操作之前,解析器执行 $$ = ;
的等价物。一旦动作完成,</code> 到 <code>$n
被弹出堆栈,$$
被压入堆栈。实际上,这会用 $$
覆盖旧的 </code>,这就是必须使用临时变量的原因。 (否则,在操作中设置 <code>$$
会意外地使 无效。)