Sometimes when I am doing low-level development I run into interesting incompatibilities between C and C++. The type of thing you may have known at one time, but that you eventually forget unless you know the specification of both languages inside-out.
One such case happened to me today with code similar to the following:
// foo.h:
int RAM[10000];
// foo.c:
#include "foo.h"
int main() {
return (unsigned long long)RAM;
}
// bar.c
#include "foo.h"
int bar() {
return (unsigned long long)RAM;
}
This particular code is valid in C, but not valid in C++ if you link both foo.c and bar.c into the same binary. Why? Because there is a subtle difference with how C and C++ treat uninitialized global symbols (RAM[] in this case). In C, the symbols are merged into one and become a single symbol (instance of a variable) (emitted using common linkage: see here). However, in C++ there is no such thing as common linkage. If you declare the same variable twice, regardless of the circumstances, it is seen as a two separate variables that conflict. So in the latter case you will see the following error:
/tmp/ccK4Aa4B.o:(.bss+0x0): multiple definition of `RAM'
/tmp/cc8u2dAO.o:(.bss+0x0): first defined here
collect2: error: ld returned 1 exit status
Of course, you can get around the limitation in C++ by doing the following instead:
// foo.h:
extern int RAM[10000]; //modified
// foo.c:
#include "foo.h"
int RAM[10000]; //added
int main() {
return (unsigned long long)RAM;
}
// bar.c
#include "foo.h"
int bar() {
return (unsigned long long)RAM;
}
The interesting part is some of the implications for C programmers. Suppose for example, you borrowed a database implementation that employed common linkage & you just happened to accidentally clobber over the name in your own code. In this case, you would have a subtle silent bug on your hand. The variable would be shared between your code and the database and you might never know! Here's another example shows some interesting things that occur in these cases:
// baz.c:
#include <stdio.h>
int test; //4 byte declaration.
unsigned long long qux();
int main()
{
printf("wrong size returned in baz: %d\n", sizeof(test)); //Oops!
qux();
printf("set value in baz: %d\n", test);
return test;
}
// qux.c:
#include <stdio.h>
unsigned long long test; //8 byte declaration.
unsigned long long test;//you can redeclare without error.
unsigned long long qux() {
test = 0xFF;
printf("correct size in qux: %d\n", sizeof(test));
printf("set value in qux: %d\n", test);
}
Output:
wrong size returned in baz: 4
correct size in qux: 8
set value in qux: 255
set value in baz: 255
There are a few interesting things to note: (1) the variable is declared twice in qux.c without error, (2) is declared once with a different sized type in baz.c, (3) has actually been merged and is eight bytes large. Yet the wrong size will be printed in baz.c and the correct size only in qux.c. So it is silent even with incompatible types. The final thing to note is that even if you tried to enable verbose warnings in the compiler, you still won't see this.
The Evil Overlord,
yowanvista and
+theblazingangel
Question
snaphat (Myles Landwehr) Member
Sometimes when I am doing low-level development I run into interesting incompatibilities between C and C++. The type of thing you may have known at one time, but that you eventually forget unless you know the specification of both languages inside-out.
One such case happened to me today with code similar to the following:
This particular code is valid in C, but not valid in C++ if you link both foo.c and bar.c into the same binary. Why? Because there is a subtle difference with how C and C++ treat uninitialized global symbols (RAM[] in this case). In C, the symbols are merged into one and become a single symbol (instance of a variable) (emitted using common linkage: see here). However, in C++ there is no such thing as common linkage. If you declare the same variable twice, regardless of the circumstances, it is seen as a two separate variables that conflict. So in the latter case you will see the following error:
Of course, you can get around the limitation in C++ by doing the following instead:
The interesting part is some of the implications for C programmers. Suppose for example, you borrowed a database implementation that employed common linkage & you just happened to accidentally clobber over the name in your own code. In this case, you would have a subtle silent bug on your hand. The variable would be shared between your code and the database and you might never know! Here's another example shows some interesting things that occur in these cases:
Output:
There are a few interesting things to note: (1) the variable is declared twice in qux.c without error, (2) is declared once with a different sized type in baz.c, (3) has actually been merged and is eight bytes large. Yet the wrong size will be printed in baz.c and the correct size only in qux.c. So it is silent even with incompatible types. The final thing to note is that even if you tried to enable verbose warnings in the compiler, you still won't see this.
Link to comment
Share on other sites
41 answers to this question
Recommended Posts