-2

I'm trying to write a small doubly linked lists program in C, but for some reason it gives me undefined behavior for the first element. I want it to have an empty cell at the beginning which links the first and last elements. So it's like this: ... <-> Second Last <-> Last <-> Empty Cell <-> First <-> Second <->...

The first element is a random value but the next ones work. For example, if my input file is 1 2 3 4 5, the output will be <undefined> 2 3 4 5, where undefined can be any number C wishes to give me.

The weird part is that it works flawlessly in debug mode too (using MinGW Developer Studio as I got used to it from school). It also works good under Linux (using gcc for compilation).

This is the code:

#include <stdio.h>
#include <stdlib.h>

typedef struct Nod {
struct Nod *next, *ant;
int x;
} Nod_t, *List_t, **AList_t;

void PrintList (List_t sant){
List_t lista = sant->next;
while(lista != sant){
    printf("%i ", lista->x);
    lista = lista->next;
}
}

List_t PopulateList(char* fis){

List_t lista, sant;
int nr;
FILE *f = fopen(fis, "rt");

sant = (List_t)malloc(sizeof(List_t));
sant->next = sant->ant = NULL;
lista = sant;
//First node

while(!feof(f)){
    fscanf(f, "%i", &nr);
    lista->next = (List_t)malloc(sizeof(List_t));
    lista->next->x = nr;
    lista->next->ant = lista;
    lista = lista->next;
}

sant->ant = lista;
lista->next = sant;

return sant;

}

int main (){

List_t lista1;
lista1 = PopulateList("1.txt");
PrintList(lista1);
return 0;
}

Way better indentation here: http://pastebin.com/NVQqaYHK

AstroCB
  • 12,337
  • 20
  • 57
  • 73
  • 6
    UB is tied to the language, not to the OS (well, more or less, anyway). If it was UB on Windows, chances are it's UB on Linux as well. You might want to rephrase your title like "this code crashed on Windows but not on Linux, I think it invokes UB". Also, format your code (whitespace, indentation, etc.), because it's hard to read as-is. Furthermore, **decide which language you are writing your program in.** Is this C ***XOR*** C++? It can't be both at the same time, they are very distinct and different languages. –  Sep 11 '13 at 16:47
  • See [this answer](http://stackoverflow.com/a/7937638/841108) for general tips about C or C++ programming on Linux. – Basile Starynkevitch Sep 11 '13 at 16:48
  • I think it has something to do with the fact that uninitialized variables on Linux are set to 0, while on windows are given a random value. Also, this is C, not C++. I tagged it as C++ as it's a small code. – Pirvu Mihai-Cristian Sep 11 '13 at 16:49
  • 3
    @PirvuMihai-Cristian *"uninitialized variables on Linux are set to 0"* <-- This is not accurate at all, at least not with any Linux C compiler I have ever used. – cdhowie Sep 11 '13 at 16:49
  • Probably I'm very wrong about it, but try to make a malloc on a random typedef struct variable and then print an element of it. Under Linux it will print 0, while under Windows will go full random. Talking about gcc (the one that comes with Linux mint 15) – Pirvu Mihai-Cristian Sep 11 '13 at 16:51
  • @PirvuMihai-Cristian I don't see what the size of the code could possibly have anything to do with which language it is written in. If it's C, then tag it as C and not C++. –  Sep 11 '13 at 16:51
  • 1
    @PirvuMihai-Cristian You were only (un)lucky. –  Sep 11 '13 at 16:51
  • I see, shall I repost it or what? Though I posted it under C++ as well because any C++ compilator should compile it too, right? – Pirvu Mihai-Cristian Sep 11 '13 at 16:52
  • @H2CO3: Technically, whether or not something is UB *can* depend on implementation-defined aspects of the implementation. For example: `long x = 1; printf("%d", *((char *)&x + 4));` invokes UB if and only if `sizeof(long)<=4`. – R.. GitHub STOP HELPING ICE Sep 11 '13 at 16:56
  • 1
    @PirvuMihai-Cristian C++ is not a superset of C, it is a completely different language with many different rules. For example `int *x = malloc(sizeof(int));` is legal C, but causes a compile-time error in C++. – cdhowie Sep 11 '13 at 16:56
  • @R.. I am perfectly aware of that :) That's why I included the "well, more or less, anyway" part in my comment. –  Sep 11 '13 at 16:57
  • @PirvuMihai-Cristian No problem, I have retagged the question so now it doesn't include "C++". –  Sep 11 '13 at 16:57
  • 1
    I'm curious, what does the "t" in fopen(x, "rt") mean? – Charlie Burns Sep 11 '13 at 16:58
  • "text", as opposed to "binary", hmm ? What obvious mistake am I missing here? – Pirvu Mihai-Cristian Sep 11 '13 at 16:59

2 Answers2

1

Try changing your mallocs from

sant = (List_t)malloc(sizeof(List_t));

to

sant = malloc(sizeof(Nod_t));

sant = malloc(sizeof(List_t)); returns a pointer to area the size of a pointer.

sant = malloc(sizeof(Nod_t)); returns a pointer to area the size of Nod_t.

By the way, it's a matter of style, but your type aliases are confusing and probably led to your typo in the mallocs. I'd suggest just using the Nod_t type and get rid of List_t and AList_t. Consider these declarations:

List_t foo; // is this a pointer or a struct? Can't tell from the decl, need to know the typedef.
Nod_t *foo; // obviously a pointer

See also Zack's suggestions, especially concerning the feof() issues.

Charlie Burns
  • 6,994
  • 20
  • 29
  • This is correct (except for casting the result of `malloc`, which one should not do) but is only part of the problem. – zwol Sep 11 '13 at 17:08
  • Yes, see the issues raised by Zack. – Charlie Burns Sep 11 '13 at 17:11
  • Thanks you for pointing my obvious mistake, the sizeof of List_t is a pointer to Nod_t. It's exactly as saying: `int *x; x = (int*)malloc(sizeof(int*));` – Pirvu Mihai-Cristian Sep 11 '13 at 17:11
  • About the casting of malloc, our teacher said that it is better to do it like this since malloc returns void*. I have kept that tradition even though I'm sure I read that C compilers cast them to the correct type of pointer. – Pirvu Mihai-Cristian Sep 11 '13 at 17:16
  • Also, compare the return value of fscanf() to 1 (the expected number items to be read) to avoid the unintended node in your doubly linked circular list. – Adam Burry Sep 11 '13 at 17:28
  • Regarding casting the return value of `malloc`, when you are writing C-that-does-not-also-have-to-compile-as-C++, your teacher is **wrong**. Casting return values hides bugs. – zwol Sep 11 '13 at 20:33
1

The bug is here:

sant = (List_t)malloc(sizeof(List_t));
sant->next = sant->ant = NULL;
lista = sant;

You do not initialize sant->x, so you have an entry in the list (before all the entries containing real data from the file) with an uninitialized x.

When you later print out x for all the entries in the list, that triggers undefined behavior (on both Linux and Windows, to be clear; undefined behavior includes the possibility that the value printed will always be zero).

You should restructure the loop immediately after the code shown, so that you don't allocate the first entry in the list until after you've read the first line in the file, and you don't have this initial list entry with an uninitialized value.

You also need the bugfix mentioned by Charlie Burns.

zwol
  • 135,547
  • 38
  • 252
  • 361
  • No, as I said the goal is to have a NULL value Cell at the beginning. The original code used to have "x" as an void*, not int, so it would be NULL (or a pointer to a sorting function), but this is just a small code. – Pirvu Mihai-Cristian Sep 11 '13 at 17:09
  • Oh, you're actually doing that on purpose! That's a sensible thing to do, but then you should explicitly initialize `sant->x` to `0` (or `-1` or some other concrete value -- it doesn't matter what) to avoid UB if the header cell ever gets printed. – zwol Sep 11 '13 at 17:12
  • Oh, and, I just noticed, your `while (!feof(f))` loop is wrong. `feof` only returns true if you have *tried to read beyond the end* of the file. It does *not* return true if you have read *up to* the end of the file. You should change it to `for (;;)` and put `if (feof(fp)) break;` immediately after the `fscanf`. (This ought to be causing a second garbage value at the *end* of the list.) – zwol Sep 11 '13 at 17:15
  • I'm aware of that too as if I leave a \n or even a space after the last element in the file, it will just read again an empty value. This bug makes it that the last value is read twice. I will while(fscanf(...)) or as you said infinite loop and break. – Pirvu Mihai-Cristian Sep 11 '13 at 17:17
  • Really you ought to be using `fgets` and `strtol` instead of `scanf`. You have to write more code but it's easier to be sure the code does what you want. – zwol Sep 11 '13 at 22:38