Live data from Hacker News

Modernizing C arrays for greater memory safety: a case study in the Linux kernel

people.kernel.org

21–30 of 126 posts

Re: Modernizing C arrays for greater memory safety: a case study in the Linux kernel

#22
post #15
post #7

Earlier quoted context omitted.

According to the C spec, zero length arrays are explicitly illegal. > Zero-length array declarations are not allowed, even though some compilers offer them as extensions (typically as a pre-C99 implementation of flexible array members). However, as they say, gcc (and therefore clang) have an extension that allows it. So does MSVC but it works slightly differently.

you should be specific about which C spec you are referring to, you talk about "the C spec" and then you mention the "pre-C99 implementation". Maybe you mean they've always been illegal in every version, but it would be more clear.

The bit after `>` is a quote, not my words. See https://en.cppreference.com/w/c/language/array for the source

It's saying that C99 implemented flexible array members but before then some compilers introduced their own (nonstandard) implementation of flexible array members, using the (not allowed) zero sized array notation.

Re: Modernizing C arrays for greater memory safety: a case study in the Linux kernel

#23
For code that is critical to performance, C99's "flexible array at the end of a struct" is an useful tool. It basically allows you to attach a header at the beginning of some dynamically-allocated binary data of infinite length (yes, it can be implemented as a pointer at the end of the struct, but the extra latency of another pointer chasing can reduce performance). Before C99, the "size-1 hack" or "size-0 GCC extension" for this purpose was already widespread in both the Linux kernel and Windows [1], but with the disadvantage of triggering memory-safety tools, as the author pointed out.

Meanwhile, unlike C99, this construction is not allowed by any version of the C++ standards, any such use would be a non-standard extension, I think this is unfortunate. I only write C, I wonder if any C++ guru out there can answer this question: does modern C++ have a better solution to implement the same thing?

[1] https://devblogs.microsoft.com/oldnewthing/20040826-00/?p=38...

Re: Modernizing C arrays for greater memory safety: a case study in the Linux kernel

#24
post #8

Earlier quoted context omitted.

Hang on, let me think this through... If malloc(0) gets called as first malloc in the program the system break does not need to be moved, as there is always 0 bytes space available... but malloc does like to move sysbreak by a large amount at a time to reduce the need for repeated calls... I'm guessing malloc(0) does not move sysbreak and simply returns a pointer to the bottom of the heap?

Implementation defined. I've heard of returning null (under the case that your free() implementation allows nulls to be passed in) or returning a pointer to a zero length object on the heap like you're suggesting. Really just about the only requirement is that the pointer can subsequently be given to free() since dereferencing the pointer is UB.

free(NULL) is required to be a no-op by the ISO C standard.

Re: Modernizing C arrays for greater memory safety: a case study in the Linux kernel

#25

Call me crazy, but zero length arrays are a great abstraction, when you working with implicit data-structures. Not safe, but elegant and performant. Many codebases could be 2x faster if their designers embraced that concept.

> Not safe, but elegant and performant.

I'd say it's also not more dangerous (or equally dangerous, depending on your camp) than a pointer from malloc().

Re: Modernizing C arrays for greater memory safety: a case study in the Linux kernel

#26
post #8
post #4

Earlier quoted context omitted.

both c and c++ have the concept of zero-length arrays, if you malloc them - int * a = malloc(0); is ok

Hang on, let me think this through... If malloc(0) gets called as first malloc in the program the system break does not need to be moved, as there is always 0 bytes space available... but malloc does like to move sysbreak by a large amount at a time to reduce the need for repeated calls... I'm guessing malloc(0) does not move sysbreak and simply returns a pointer to the bottom of the heap?

Just want to point out malloc(0) might gives you any pointer, and you still have to call free() on it.

Re: Modernizing C arrays for greater memory safety: a case study in the Linux kernel

#27
post #4

Earlier quoted context omitted.

both c and c++ have the concept of zero-length arrays, if you malloc them - int * a = malloc(0); is ok

malloc() doesn't allocate arrays. It allocates blocks of memory. Hence sizeof doesn't work the same for malloc() objects as it does on arrays.

sizeof doesn't work the same for malloc() because the type of the returned value is a pointer, and the behavior of sizeof is dependent solely on the static type. For comparison, calloc() is specifically defined as "allocates space for an array of ... objects" in the Standard, but since return type is still void*, the caveat with sizeof still applies.

Re: Modernizing C arrays for greater memory safety: a case study in the Linux kernel

#28
post #22
post #15

Earlier quoted context omitted.

you should be specific about which C spec you are referring to, you talk about "the C spec" and then you mention the "pre-C99 implementation". Maybe you mean they've always been illegal in every version, but it would be more clear.

The bit after `>` is a quote, not my words. See https://en.cppreference.com/w/c/language/array for the source It's saying that C99 implemented flexible array members but before then some compilers introduced their own (nonstandard) implementation of flexible array members, using the (not allowed) zero sized array notation.

The sequence of event was basically:

1. Pre-C99, no flexible array was allowed.

2. Gradually, people started using the "size-1 array at the end of a sturct but write beyond" hack as flexible array.

3. As an attempt to do the hack in a more ordered manner, some compilers, including GCC, started officially supporting the non-standard "size-0" array extension.

4. C99 added flexible array with indefinite length (array[]), while prohibiting both the undefined-behavior "array[1]" and the non-standard extentios "array[0]".

So when people say "size-0" array, it could mean either of these three things, and it does get a bit confusing. But fundamentally the idea was the same, all of the three techniques are used to achieve the same practical effect.

Re: Modernizing C arrays for greater memory safety: a case study in the Linux kernel

#29

For code that is critical to performance, C99's "flexible array at the end of a struct" is an useful tool. It basically allows you to attach a header at the beginning of some dynamically-allocated binary data of infinite length (yes, it can be implemented as a pointer at the end of the struct, but the extra latency of another pointer chasing can reduce performance). Before C99, the "size-1 hack" or "size-0 GCC extens…

You can do it without trailing arrays, by stacking the structures after one and other:

MyStructA a; MyStructB b;

a = malloc((sizeof a) + (sizeof b)); b = (MyStructB *)&a[1];

You need to make sure that the second struct doesn't have stricter alignment requirements than the one preceding it, but using this technique you can stack any number of structures or arrays of structures in one allocation.

(I would generally not recommend this coding style unless you have very specific requirements of memory usage)

Re: Modernizing C arrays for greater memory safety: a case study in the Linux kernel

#30

For code that is critical to performance, C99's "flexible array at the end of a struct" is an useful tool. It basically allows you to attach a header at the beginning of some dynamically-allocated binary data of infinite length (yes, it can be implemented as a pointer at the end of the struct, but the extra latency of another pointer chasing can reduce performance). Before C99, the "size-1 hack" or "size-0 GCC extens…

> Does modern C++ have a better solution to implement the same thing?

I'm no guru, but I know from experience you can do it in C++:

  {0}[calvin ~] cat test.cpp
  #include 
  #include 
  
  struct foo {
    int len;
    int v[];
  };
  
  int main(void) {
    auto p = std::unique_ptr(reinterpret_cast(
        malloc(sizeof(struct foo) + sizeof(int) * 2)));
  
    p->v[1] = 99;
    std::cerr v[1] 
EDIT: Remove unnecessary extern block, as pointed out by wahern in the replies.
Post reply on HN