-
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
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
design flaw: Access out of bounds on pointer to array #386
Comments
I don't think there is a compiler bug here, but this reveals an unfortunate design flaw in Zig. Given I'm curious to get @thejoshwolfe's thoughts here. |
Here's a proposal:
@AndreaOrru thoughts? how would this fit into zen? |
My only suggestion for the proposal is to make the I don't know if it varies between cultures, but the |
I'm still a little uneasy about the My original idea of an arithmetic pointer was that it would be a slice with no runtime-known The important things about an arithmetic pointer are that you can do |
I'm going to move this to 0.2.0. It's an important, breaking change, but status quo is working and well-defined, if a bit unintuitive. I think we can release 0.1.0 before focusing on this change. |
I stumbled into this design issue today, and can provide some feedback. Pointer arithmetic is definitely very important for some cases. I was able to use indexing instead in my case, but I did stumble on some things that were annoying. I wanted a pointer to When incrementing a pointer, doing I've always found it a bit magic too, that writing I think the proposal looks pretty good. Declaring something as an arithmetic pointer provides very useful information about what you intend to do with it. There may be interactions with null pointers to think about as well. In my experiment, I wish I could compare a null pointer with a plain pointer. They may not be the same type, but I think |
I think this is probably not the best way to communicate intent here. If the slice is length 0 this will trigger an out of bounds panic. The -1, +1 is clearly a workaround and not directly communicating what you want. If you really just want a pointer to Help me understand though, why do you need a pointer to the address after the valid data? As for the use case of incrementing a pointer, I want to understand this use case better. If you're incrementing a pointer, that means that there is something that specifies how many items after the base pointer are valid. If that value is known ahead of time, then a slice is appropriate. If it is null terminated (see related issue #265) or otherwise found out lazily, you can use an index to march forward, like the implementation of strlen in std.cstr.len: pub fn len(ptr: &const u8) -> usize {
var count: usize = 0;
while (ptr[count] != 0) : (count += 1) {}
return count;
}
We can definitely make this work. What we'll do is implement the equality operators for |
That's what I originally tried, but that triggers bounds checking. I don't think that wrapping the code in something that disables bounds checking is nice either.
If you have a buffer that you're consuming items from one at a time, you can roughly do it two ways. 1: Have a pointer to the start of the buffer, an index of your current item, and the length or end pointer (3 words) 2: Have a pointer to the next item to consume, and the length or end pointer (2 words) The second approach saves you a word. You could do it without incrementing a pointer: you could have a pointer to the end, and a counter of how many items are left. But that's a bit silly. You could elegantly do the second approach with a slice. It doesn't really have to be easy to do pointer arithmetic, if the standard library includes generic structures that abstracts away super-optimized implementations of various datastructures and algorithms, it's not that necessary. Look at the code related to 'uartBufferPtr' here to see what I'm doing now. I'm currently using an end-pointer rather than length or slice: https://github.com/skyfex/zig-nrf-demo/blob/master/test.zig Looking at the code now, I'm not sure I actually mind doing |
Did you see the const assert = @import("std").debug.assert;
test "get end ptr of slice" {
var array = []u8{1, 2, 3, 4};
var slice = array[0..];
const ptr_to_end = &slice.ptr[slice.len];
assert(@ptrToInt(ptr_to_end) == @ptrToInt(&slice[0]) + 4);
}
I think this is the best way to represent this. You're right that it has this downside of storing both words. Sometimes the optimizer is able to avoid this, sometimes not. Is this code reasonable? diff --git a/test.zig b/test.zig
index 464d9f4..0cd7e55 100644
--- a/test.zig
+++ b/test.zig
@@ -130,8 +130,9 @@ fn NVIC_DisableIRQ(IRQn: c_int) {
// -- UART Driver --
var uartBusy: bool = false;
-var uartBufferPtr: &const u8 = undefined;
-var uartBufferEnd: &const u8 = undefined;
+
+var uartBuffer: []const u8 = undefined;
+var uartBuffer_index: usize = undefined;
fn uartInit() {
// NVIC_SetPriority(PWM_IRQn, 2);
@@ -152,11 +153,9 @@ export fn UART0_IRQHandler() {
}
fn uartSendBufferByte() {
- if (@ptrToInt(uartBufferPtr) <= @ptrToInt(uartBufferEnd)) {
- nrfUart.TXD = *uartBufferPtr;
- uartBufferPtr = &uartBufferPtr[1];
- }
- else {
+ if (uartBuffer_index < uartBuffer.len) {
+ nrfUart.TXD = uartBuffer[uartBuffer_index];
+ } else {
uartBusy = false;
nrfGpio.OUT = nrfGpio.OUT ^ (1<<4);
}
@@ -165,8 +164,8 @@ fn uartSendBufferByte() {
fn uartSendString(str: []const u8) {
while (uartBusy) {}
uartBusy = true;
- uartBufferPtr = &str[0];
- uartBufferEnd = &str[str.len-1];
+ uartBuffer_index = 0;
+ uartBuffer = str;
uartSendBufferByte();
} |
Ah, I missed ".ptr", yes. The alternate code is reasonable yes. If I wasn't playing around, and just wanted clear/simple code, I'd probably go for something like that. But when Zig becomes a decent alternative to C, I know there'll be people who will want to do hardcore hand optimisations, so it's interesting to see how natural that is in Zig. The safest way should be the easiest though. |
Unclear behaviour on array access
The text was updated successfully, but these errors were encountered: