-
Notifications
You must be signed in to change notification settings - Fork 27
Mpf get str fixups #25
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
from https://gmplib.org/manual/Converting-Floats.html : `Function: char * mpf_get_str (char *str, mp_exp_t *expptr, int base, size_t n_digits, const mpf_t op)`
1113816 to
e2c9bde
Compare
src/mpf.rs
Outdated
| } | ||
| } else { | ||
| // From mpf/get_str.c. | ||
| let c_str = CString::new(vec![37; n_digits as usize + 2]).unwrap(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This intermediate CString is just overhead—why not use Vec<c_char>::as_ptr directly?
src/mpf.rs
Outdated
| let c_str = CString::new(vec![37; n_digits as usize + 2]).unwrap(); | ||
| unsafe { | ||
| let p = __gmpf_get_str(c_str.as_ptr(), exp, base, n_digits, &self.mpf); | ||
| CString::from_raw(p).to_str().unwrap().to_string() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doesn’t this double-free the string, once via c_str and once via the CString? I think you want CStr::from_ptr.
|
|
||
| pub fn to_str(&self) -> Result<&str, str::Utf8Error> { | ||
| let bytes: &[u8] = unsafe { slice::from_raw_parts(self.0, self.1) }; | ||
| str::from_utf8(&bytes[..bytes.len() - 1]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CStr::from_ptr is more direct and avoids the separate strlen.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no matter what, the length needs to be known in order to call the free func, so it's either strlen or CStr::to_bytes().len(). i think strlen communicates more clearly how the length param to free_func is computed, whereas the alternate method relies on knowing that CStr implicitly calls strlen behind the scenes.
src/mpf.rs
Outdated
| } else { | ||
| // From mpf/get_str.c. | ||
| let bytes = unsafe { | ||
| let mut bytes: Vec<u8> = uninitialized(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You need a Vec with uninitialized data, not an uninitialized Vec! I think Vec::with_capacity is the right thing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ugh, thanks for catching this.
src/mpf.rs
Outdated
| // From mpf/get_str.c. | ||
| let bytes = unsafe { | ||
| let mut bytes: Vec<u8> = uninitialized(); | ||
| let c_str: *mut c_char = transmute(bytes.as_mut_ptr()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need for a worrying transmute—you can just bytes.as_mut_ptr() as *mut c_char.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pretty sure that as_mut_ptr will return a * mut u8.
src/mpf.rs
Outdated
| __gmpf_get_str(c_str, exp, base, n_digits, &self.mpf); | ||
| bytes | ||
| }; | ||
| String::from_utf8(bytes).unwrap() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That includes the trailing NUL, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yep. from mpf/get_str.c:
if (dbuf == 0)
{
/* We didn't get a string from the user. Allocate one (and return
a pointer to it) with space for `-' and terminating null. */
alloc_size = n_digits + 2;
dbuf = (char *) (*__gmp_allocate_func) (n_digits + 2);
src/mpf.rs
Outdated
| } else { | ||
| let bytes = unsafe { | ||
| // n_digits + 2 from mpf/get_str.c. | ||
| let mut bytes: Vec<u8> = vec![uninitialized(); n_digits as usize + 2]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This create a single uninitialized byte, then copies it n_digits + 2 times, so you haven’t really saved any work.
I think what you want is
let mut buf = Vec::with_capacity(n_digits as usize + 2);
let ptr = __gmpf_get_str(buf.as_mut_ptr(), exp, base, n_digits, &self.mpf);
CStr::from_ptr(ptr).to_str().unwrap().to_string()There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i think i'll revert to vec![0; n_digits + 2], which will call rust_allocate_zeroed which i'm guessing is more or less calloc. the with_capacity trick is neat but seems a little unsafe, and to be honest, i'm not sure that enough digits are requested for an appreciable amount of work to be saved.
src/mpf.rs
Outdated
| __gmpf_get_str(c_str, exp, base, n_digits, &self.mpf); | ||
| bytes | ||
| }; | ||
| String::from_utf8(bytes).unwrap() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No no, my point here was that String::from_utf8 includes the trailing NUL in its return value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, i realized what you meant a few minutes after hitting send. this branch should be tested anyway, and it shows up in the test.
this incorrectly created an uninitialized Vec, and using uninitialized isn't worth it anyway.
851473b to
9483bf6
Compare
src/ffi.rs
Outdated
| use std::ptr::null_mut; | ||
| unsafe { | ||
| let mut free_func: FreeFunc = mem::uninitialized(); | ||
| __gmp_get_memory_functions(null_mut(), null_mut(), mem::transmute(&mut free_func)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This transmute is unnecessary because &mut FreeFunc converts implicitly to *mut FreeFunc.
9483bf6 to
58d47c2
Compare
bug was introduced in 877104f.
the intent here is to allocate enough space for gmpf_get_str to plop a string. if all digits are requested (n_digits == 0), we defer to gmpf_get_str itself to figure out how much space is needed (because the limb nonsense is too tedious to deal with) by passing nullptr to the first argument, as documented in the manual:
if the number of digits requested is known (non-zero n_digits), then simply allocate a correctly sized cstring (unlike 877104f, which allocates a 0-byte string).
to avoid memory leaks from repeated get_str(n_digits=0) calls, a wrapper is used to de-allocate on drop the gmp-allocated string.
with this patch, #24 passes.