6 ms·
There's nothing surprising in the first example. Comparing the addresses of stack variables is undefined behaviour. The second one is more interesting: ex
by tzahola 8y ago
There's nothing surprising in the first example. Comparing the addresses of stack variables is undefined behaviour.
The second one is more interesting:
extern int _start[];
extern int _end[];
void foo(void) {
for (int *i = _start; i != _end; ++i) { /* ... */ }
}
GCC optimized "i != _end" into "true". The kernel guys fixed this by turning "_start" and "_end" into "extern int*". I always thought [] was just syntactic sugar over a regular pointer, but seems like I was wrong.
- JdeBP 8y agoActually, the headline article tells us that it optimized it to true. That affected some of my code from years ago, too; and another fix is to keep _start and instead of having another array called _end have an external size_t giving the number of elements of start and loop while i!=_start+count . In fairness to the compiler people there shouldn't really be anything surprising in the second example, either. There are x86 memory models (compact and large) where that loop never terminates in the general case, because the two arrays are not in the same data segment and no amount of incrementing a (far but not huge) pointer will get from one to the other. So it's not the case that this is a new pitfall. It was a pitfall decades ago. People just thought that it didn't exist with modern tiny (a.k.a. flat) memory models, because the old explanation wasn't applicable. It does, but with a different explanation.
- tzahola 8y agoYup, I've mistyped "true" as "false". If you set "_start" and "_end" to addresses of different objects, then you're invoking undefined behaviour and all bets are off. But this should be perfectly valid IMO: extern char* _start; extern char* _end; void foo() { for (char* c = start; c != end; c++) ... } int main(int, char**) { char buf[128]; _start = &buf[0]; _end = &buf[128]; foo(); } Right?
- JdeBP 8y agoThe thing there is indeed not the validity, but the introduction of extra memory references and variables. The code with the arrays compiles to immediate value loads with load-time fixups to the code; and if this is linking to an assembly language module the necessary assembly language is just some labels against the data. The code with the pointers compiles to loads from data memory, with (in the more usual paradigm where the start and end are bracketing some vector of initialized data in the program image) load-time fixups to the initial values of the variables; and the assembly language has to be different, some variables with these labels whose initial values are the addresses of some other labels against the actual data. Notice that the Linux Kernel developer in 2016 did not immediately appreciate the need to modify the concomitant assembly language and linker script stuff to match the change in the C language code. The approach that I mentioned keeps the immediate value for the start, and uses a load from initialized data memory for the count. Other approaches are possible, but they need more work in the build process. For example: By generating the C/C++ language declaration and definition from a script or suchlike, one could calculate and declare the number of elements in the array, making it possible to use sizeof.
- kbp 8y ago> I always thought [] was just syntactic sugar over a regular pointer, but seems like I was wrong. For another example showing their differences, if a.c has: int n[] = { 1 }; And b.c has: extern int *n; printf("%d\n", *n); Then running b.c will segfault, whereas if it said extern int n[], it would work as expected (hint: extern int n and printf("%d\n", n) would also work). Arrays degrade to pointers at the drop of a hat, but they're distinct from them.
- jcelerier 8y ago> Then running b.c will segfault why would it not ? On one side you declare an int array of size 1, on the other side you declare a pointer. Think about what's going on with the bytes : int n[] = { 1 }; will look exactly the same in memory than int n = 1; which is of course not compatible with int* n = 1; Arrays by themselves have no indirection in C. => https://godbolt.org/g/xwT7uW https://godbolt.org/g/xwT7uW
- kbp 8y agoRight, that's what I was explaining. A lot of people get tripped up by this because arr is equivalent to &arr, int arr[] is equivalent to int *arr as a formal parameter, etc, so many people go around with the idea that arrays are just pointers in C, like in the comment I quoted; it's an easy thing to get confused by. I was giving an example to demonstrate how they differ.
- mwkaufma 8y agoThe confusion in the example is the choice of names "start" and "end". If we rename them "some_array" and "some_totally_different_array" the undefined behavior is clear.
- jcelerier 8y ago> I always thought [] was just syntactic sugar over a regular pointer, but seems like I was wrong. It depends on the context. When used in function arguments, yes, it's the same. When used during the declaration of a variable, it's entirely different.
- jjnoakes 8y ago> Comparing the addresses of stack variables is undefined behaviour Can you elaborate? I don't think this is true.
- Rusky 8y agoThey're from different objects, so it's not meaningful (according to the standard) to compare them. That's what makes it UB.
- jjnoakes 8y ago"Not meaningful" and "undefined behavior" have very different meanings. I agree it's not meaningful, but the standard does define the result of comparing pointers to two unrelated objects with "==", so it's not UB.
- Rusky 8y agoWhere? The post cites C11 section 6.5.8 paragraph 5, which seems to disagree with you. Of course == comparing unrelated pointers is something you need to be able to do, but relational comparisons (including those based on pointer arithmetic) don't seem to be defined anywhere...
- jjnoakes 8y agoRight, comparisons with < and > are UB, but I'm talking about ==, which is well-defined. It's hard to tell which operator "tzahola" was originally talking about, but I took "comparing the address" in this context (especially given his example code) to mean "==", or at least, include it, which makes the statement "comparing the addresses of stack variables is undefined behavior" incorrect in this context.
- tzahola 8y agoYou're right. There's no undefined behaviour for ==: Two pointers compare equal if and only if both are null pointers, both are pointers to the same object (including a pointer to an object and a subobject at its beginning) or function, both are pointers to one past the last element of the same array object, or one is a pointer to one past the end of one array object and the other is a pointer to the start of a different array object that happens to immediately follow the first array object in the address space.